From 642bf172449410bbef3a21861e6b5abe0458e4f0 Mon Sep 17 00:00:00 2001 From: javier Date: Wed, 28 Jan 2026 13:21:38 +0100 Subject: [PATCH 1/2] Several financial indicators recipes. First draft. Needs to refine --- documentation/cookbook/demo-data-schema.md | 1 - documentation/cookbook/sql/finance/atr.md | 90 +++++++++++++++++ .../cookbook/sql/finance/bid-ask-spread.md | 77 +++++++++++++++ .../cookbook/sql/finance/donchian-channels.md | 69 +++++++++++++ documentation/cookbook/sql/finance/index.md | 89 +++++++++++++++++ .../cookbook/sql/finance/keltner-channels.md | 88 +++++++++++++++++ documentation/cookbook/sql/finance/macd.md | 85 ++++++++++++++++ .../cookbook/sql/finance/maximum-drawdown.md | 96 +++++++++++++++++++ documentation/cookbook/sql/finance/obv.md | 69 +++++++++++++ .../cookbook/sql/finance/rate-of-change.md | 55 +++++++++++ .../sql/finance/realized-volatility.md | 80 ++++++++++++++++ documentation/cookbook/sql/finance/rsi.md | 79 +++++++++++++++ .../cookbook/sql/finance/stochastic.md | 82 ++++++++++++++++ documentation/sidebars.js | 40 ++++++-- 14 files changed, 993 insertions(+), 7 deletions(-) create mode 100644 documentation/cookbook/sql/finance/atr.md create mode 100644 documentation/cookbook/sql/finance/bid-ask-spread.md create mode 100644 documentation/cookbook/sql/finance/donchian-channels.md create mode 100644 documentation/cookbook/sql/finance/index.md create mode 100644 documentation/cookbook/sql/finance/keltner-channels.md create mode 100644 documentation/cookbook/sql/finance/macd.md create mode 100644 documentation/cookbook/sql/finance/maximum-drawdown.md create mode 100644 documentation/cookbook/sql/finance/obv.md create mode 100644 documentation/cookbook/sql/finance/rate-of-change.md create mode 100644 documentation/cookbook/sql/finance/realized-volatility.md create mode 100644 documentation/cookbook/sql/finance/rsi.md create mode 100644 documentation/cookbook/sql/finance/stochastic.md diff --git a/documentation/cookbook/demo-data-schema.md b/documentation/cookbook/demo-data-schema.md index f46159aec..6e97ed617 100644 --- a/documentation/cookbook/demo-data-schema.md +++ b/documentation/cookbook/demo-data-schema.md @@ -214,7 +214,6 @@ The FX dataset includes several materialized views providing pre-aggregated data #### FX trades OHLC - **`fx_trades_ohlc_1m`** - OHLC candlesticks from trade executions at 1-minute intervals -- **`fx_trades_ohlc_1h`** - OHLC candlesticks from trade executions at 1-hour intervals - **`fx_trades_ohlc_1d`** - OHLC candlesticks from trade executions at 1-day intervals These views are continuously updated and optimized for dashboard and analytics queries on FX data. diff --git a/documentation/cookbook/sql/finance/atr.md b/documentation/cookbook/sql/finance/atr.md new file mode 100644 index 000000000..27d4d77c6 --- /dev/null +++ b/documentation/cookbook/sql/finance/atr.md @@ -0,0 +1,90 @@ +--- +title: ATR (Average True Range) +sidebar_label: ATR +description: Calculate Average True Range to measure market volatility for position sizing and stop-loss placement +--- + +Average True Range (ATR) measures market volatility by calculating the average of true ranges over a period. Unlike simple high-low range, true range accounts for gaps between periods, making it more accurate for volatile markets. + +## Problem + +You want to measure volatility to set appropriate stop-losses or position sizes. Simple high-low range misses overnight gaps, and standard deviation assumes normal distribution which markets don't follow. + +## Solution + +```questdb-sql demo title="Calculate 14-period ATR" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH with_prev AS ( + SELECT + timestamp, + symbol, + high, + low, + close, + lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) AS prev_close + FROM market_data_ohlc_15m + WHERE symbol = @symbol + AND timestamp > @lookback +), +true_range AS ( + SELECT + timestamp, + symbol, + high, + low, + close, + greatest( + high - low, + abs(high - prev_close), + abs(low - prev_close) + ) AS tr + FROM with_prev + WHERE prev_close IS NOT NULL +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(tr, 6) AS true_range, + round(avg(tr, 'period', 14) OVER (PARTITION BY symbol ORDER BY timestamp), 6) AS atr +FROM true_range +ORDER BY timestamp; +``` + +The query: +1. Gets previous close using `lag()` to detect gaps +2. Calculates true range as the greatest of: + - Current high - current low (intraday range) + - |Current high - previous close| (gap up) + - |Current low - previous close| (gap down) +3. Applies 14-period EMA smoothing to get ATR + +## Interpreting results + +- **High ATR**: Market is volatile, use wider stops +- **Low ATR**: Market is quiet, can use tighter stops +- **Rising ATR**: Volatility increasing, often during trends or breakouts +- **Falling ATR**: Volatility decreasing, often during consolidation + +## Common uses + +**Stop-loss placement:** +```sql +-- Stop at 2x ATR below entry +entry_price - 2 * atr AS stop_loss +``` + +**Position sizing:** +```sql +-- Risk 1% of account, sized by ATR +(account_size * 0.01) / atr AS position_size +``` + +:::info Related documentation +- [EMA window function](/docs/query/functions/window-functions/reference/#avg) +- [lag() function](/docs/query/functions/window-functions/reference/#lag) +- [greatest() function](/docs/query/functions/numeric/#greatest) +::: diff --git a/documentation/cookbook/sql/finance/bid-ask-spread.md b/documentation/cookbook/sql/finance/bid-ask-spread.md new file mode 100644 index 000000000..0fd606403 --- /dev/null +++ b/documentation/cookbook/sql/finance/bid-ask-spread.md @@ -0,0 +1,77 @@ +--- +title: Bid-ask spread +sidebar_label: Bid-ask spread +description: Calculate bid-ask spread metrics for transaction cost analysis and liquidity measurement +--- + +The bid-ask spread is the difference between the best ask (lowest sell price) and best bid (highest buy price). It represents the cost of immediately executing a round-trip trade and is a key measure of market liquidity. + +## Problem + +You want to measure market liquidity and transaction costs. Narrow spreads indicate liquid markets with low trading costs, while wide spreads suggest illiquidity or market stress. + +## Solution + +```questdb-sql demo title="Calculate bid-ask spread metrics" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('h', -1, now()) + +SELECT + timestamp, + symbol, + round(bid_price, 5) AS bid, + round(ask_price, 5) AS ask, + round(ask_price - bid_price, 6) AS spread_absolute, + round((ask_price - bid_price) / ((bid_price + ask_price) / 2) * 10000, 2) AS spread_bps, + round((bid_price + ask_price) / 2, 5) AS mid_price +FROM core_price +WHERE symbol = @symbol + AND timestamp > @lookback +ORDER BY timestamp; +``` + +The query calculates: +- **Absolute spread**: ask - bid +- **Spread in basis points**: spread / mid_price × 10,000 +- **Mid price**: (bid + ask) / 2 + +## Aggregated spread analysis + +```questdb-sql demo title="Average spread by time period" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('d', -1, now()) + +SELECT + timestamp, + symbol, + round(avg((ask_price - bid_price) / ((bid_price + ask_price) / 2) * 10000), 2) AS avg_spread_bps, + round(min((ask_price - bid_price) / ((bid_price + ask_price) / 2) * 10000), 2) AS min_spread_bps, + round(max((ask_price - bid_price) / ((bid_price + ask_price) / 2) * 10000), 2) AS max_spread_bps, + count() AS quote_count +FROM core_price +WHERE symbol = @symbol + AND timestamp > @lookback +SAMPLE BY 1h +ORDER BY timestamp; +``` + +## Interpreting results + +- **Tight spread (< 1 bps for FX majors)**: Highly liquid, low transaction costs +- **Wide spread**: Illiquid or volatile period, higher transaction costs +- **Spread widening**: Often precedes or accompanies volatility +- **Intraday patterns**: Spreads typically widen during off-hours and narrow during active sessions + +:::note Spread conventions +- FX majors: typically 0.1-1.0 basis points +- FX minors: 1-5 basis points +- Crypto: varies widely, 1-50+ basis points +- Equities: often quoted in cents rather than bps +::: + +:::info Related documentation +- [SAMPLE BY](/docs/query/sql/sample-by/) +- [Aggregation functions](/docs/query/functions/aggregation/) +::: diff --git a/documentation/cookbook/sql/finance/donchian-channels.md b/documentation/cookbook/sql/finance/donchian-channels.md new file mode 100644 index 000000000..74c35c211 --- /dev/null +++ b/documentation/cookbook/sql/finance/donchian-channels.md @@ -0,0 +1,69 @@ +--- +title: Donchian Channels +sidebar_label: Donchian Channels +description: Calculate Donchian Channels to identify breakouts and trading ranges using highest high and lowest low +--- + +Donchian Channels plot the highest high and lowest low over a period, creating a channel that tracks price range. Breakouts above the upper channel or below the lower channel often signal trend continuation. + +## Problem + +You want to identify breakout levels and trading ranges. Moving averages smooth price but don't show clear breakout levels. Donchian Channels show exactly where price needs to go to break out of its recent range. + +## Solution + +```questdb-sql demo title="Calculate 20-period Donchian Channels" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(max(high) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 19 PRECEDING AND CURRENT ROW + ), 5) AS upper_channel, + round(min(low) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 19 PRECEDING AND CURRENT ROW + ), 5) AS lower_channel, + round((max(high) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 19 PRECEDING AND CURRENT ROW + ) + min(low) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 19 PRECEDING AND CURRENT ROW + )) / 2, 5) AS middle_channel +FROM market_data_ohlc_15m +WHERE symbol = @symbol + AND timestamp > @lookback +ORDER BY timestamp; +``` + +The query calculates: +- **Upper channel**: 20-period highest high +- **Lower channel**: 20-period lowest low +- **Middle channel**: Average of upper and lower + +## Interpreting results + +- **Price breaks above upper**: Bullish breakout, potential long entry +- **Price breaks below lower**: Bearish breakout, potential short entry +- **Price at middle**: Neutral zone +- **Narrow channel**: Low volatility, breakout likely coming +- **Wide channel**: High volatility, trend in progress + +:::note Turtle Trading +Donchian Channels were famously used by the Turtle Traders. Their system entered on 20-day breakouts and exited on 10-day breakouts in the opposite direction. +::: + +:::info Related documentation +- [min/max window functions](/docs/query/functions/window-functions/reference/#min) +- [Window functions overview](/docs/query/functions/window-functions/overview/) +::: diff --git a/documentation/cookbook/sql/finance/index.md b/documentation/cookbook/sql/finance/index.md new file mode 100644 index 000000000..2ed82e263 --- /dev/null +++ b/documentation/cookbook/sql/finance/index.md @@ -0,0 +1,89 @@ +--- +title: Capital Markets Recipes +sidebar_label: Overview +description: SQL recipes for financial analysis including technical indicators, volatility metrics, volume analysis, and risk measurement in QuestDB. +--- + +# Capital Markets Recipes + +This section contains SQL recipes for financial market analysis. Each recipe uses the +[demo dataset](/docs/cookbook/demo-data-schema/) available in the QuestDB web console. + +## Price-Based Indicators + +Foundation recipes for price analysis and trend identification. + +| Recipe | Description | +|--------|-------------| +| [OHLC Aggregation](ohlc.md) | Aggregate tick data into candlestick bars | +| [VWAP](vwap.md) | Volume-Weighted Average Price | +| [Bollinger Bands](bollinger-bands.md) | Price channels based on standard deviation | +| [Bollinger BandWidth](bollinger-bandwidth.md) | Measure band expansion and contraction | + +## Momentum Indicators + +Measure the speed and strength of price movements. + +| Recipe | Description | +|--------|-------------| +| [RSI](rsi.md) | Relative Strength Index for overbought/oversold conditions | +| [MACD](macd.md) | Moving Average Convergence Divergence | +| [Stochastic Oscillator](stochastic.md) | Compare closing price to price range | +| [Rate of Change](rate-of-change.md) | Percentage price change over N periods | + +## Volatility Indicators + +Quantify market uncertainty and price variability. + +| Recipe | Description | +|--------|-------------| +| [ATR](atr.md) | Average True Range | +| [Rolling Std Dev](rolling-stddev.md) | Moving standard deviation of returns | +| [Donchian Channels](donchian-channels.md) | High/low price channels | +| [Keltner Channels](keltner-channels.md) | EMA-based volatility channels | +| [Realized Volatility](realized-volatility.md) | Historical volatility from returns | + +## Volume & Order Flow + +Analyze trading activity and order flow dynamics. + +| Recipe | Description | +|--------|-------------| +| [OBV](obv.md) | On-Balance Volume | +| [Volume Profile](volume-profile.md) | Volume distribution by price level | +| [Volume Spike](volume-spike.md) | Detect abnormal volume | +| [Aggressor Imbalance](aggressor-volume-imbalance.md) | Buy vs sell pressure | + +## Risk Metrics + +Portfolio risk measurement and drawdown analysis. + +| Recipe | Description | +|--------|-------------| +| [Maximum Drawdown](maximum-drawdown.md) | Peak-to-trough decline | + +## Market Microstructure + +Analyze market quality and trading costs. + +| Recipe | Description | +|--------|-------------| +| [Bid-Ask Spread](bid-ask-spread.md) | Spread metrics and analysis | +| [Liquidity Comparison](liquidity-comparison.md) | Compare liquidity across instruments | + +## Market Breadth + +Measure overall market participation and sentiment. + +| Recipe | Description | +|--------|-------------| +| [TICK & TRIN](tick-trin.md) | Market breadth indicators | + +## Math Utilities + +General-purpose financial calculations. + +| Recipe | Description | +|--------|-------------| +| [Compound Interest](compound-interest.md) | Interest and growth calculations | +| [Cumulative Product](cumulative-product.md) | Running product for returns | diff --git a/documentation/cookbook/sql/finance/keltner-channels.md b/documentation/cookbook/sql/finance/keltner-channels.md new file mode 100644 index 000000000..99eab4406 --- /dev/null +++ b/documentation/cookbook/sql/finance/keltner-channels.md @@ -0,0 +1,88 @@ +--- +title: Keltner Channels +sidebar_label: Keltner Channels +description: Calculate Keltner Channels using EMA and ATR for volatility-based support and resistance levels +--- + +Keltner Channels are volatility-based bands set above and below an EMA. Unlike Bollinger Bands which use standard deviation, Keltner Channels use Average True Range (ATR), making them less sensitive to sudden price spikes. + +## Problem + +You want volatility bands that adapt to market conditions but are smoother than Bollinger Bands. Bollinger Bands can expand rapidly on single large moves, while Keltner Channels respond more gradually to sustained volatility changes. + +## Solution + +```questdb-sql demo title="Calculate Keltner Channels with 20 EMA and 2x ATR" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH with_prev AS ( + SELECT + timestamp, + symbol, + high, + low, + close, + lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) AS prev_close + FROM market_data_ohlc_15m + WHERE symbol = @symbol + AND timestamp > @lookback +), +with_tr AS ( + SELECT + timestamp, + symbol, + high, + low, + close, + greatest( + high - low, + abs(high - prev_close), + abs(low - prev_close) + ) AS tr + FROM with_prev + WHERE prev_close IS NOT NULL +), +with_indicators AS ( + SELECT + timestamp, + symbol, + close, + avg(close, 'period', 20) OVER (PARTITION BY symbol ORDER BY timestamp) AS ema20, + avg(tr, 'period', 10) OVER (PARTITION BY symbol ORDER BY timestamp) AS atr + FROM with_tr +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(ema20, 5) AS middle, + round(ema20 + 2 * atr, 5) AS upper, + round(ema20 - 2 * atr, 5) AS lower +FROM with_indicators +ORDER BY timestamp; +``` + +The query: +1. Calculates True Range accounting for gaps +2. Applies EMA smoothing to both price (20-period) and ATR (10-period) +3. Creates bands at ±2 ATR from the EMA + +## Interpreting results + +- **Price above upper**: Strong uptrend or overbought +- **Price below lower**: Strong downtrend or oversold +- **Price at middle (EMA)**: Mean reversion target +- **Channels widening**: Volatility increasing +- **Channels narrowing**: Volatility decreasing + +## Keltner squeeze + +When Bollinger Bands move inside Keltner Channels, it signals extremely low volatility (a "squeeze"). See the [Bollinger BandWidth recipe](/docs/cookbook/sql/finance/bollinger-bandwidth/) for measuring squeeze conditions. + +:::info Related documentation +- [Bollinger Bands](/docs/cookbook/sql/finance/bollinger-bands/) +- [ATR recipe](/docs/cookbook/sql/finance/atr/) +- [EMA window function](/docs/query/functions/window-functions/reference/#avg) +::: diff --git a/documentation/cookbook/sql/finance/macd.md b/documentation/cookbook/sql/finance/macd.md new file mode 100644 index 000000000..af14e049d --- /dev/null +++ b/documentation/cookbook/sql/finance/macd.md @@ -0,0 +1,85 @@ +--- +title: MACD (Moving Average Convergence Divergence) +sidebar_label: MACD +description: Calculate MACD indicator with signal line and histogram for trend-following momentum analysis +--- + +MACD (Moving Average Convergence Divergence) is a trend-following momentum indicator that shows the relationship between two exponential moving averages. It consists of the MACD line, signal line, and histogram. + +## Problem + +You want to identify trend changes and momentum shifts. Simple moving averages lag too much, while raw price changes are too noisy. MACD combines fast and slow EMAs to filter noise while remaining responsive to trend changes. + +## Solution + +```questdb-sql demo title="Calculate MACD with signal line and histogram" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH ema AS ( + SELECT + timestamp, + symbol, + close, + avg(close, 'period', 12) OVER (PARTITION BY symbol ORDER BY timestamp) AS ema12, + avg(close, 'period', 26) OVER (PARTITION BY symbol ORDER BY timestamp) AS ema26 + FROM market_data_ohlc_15m + WHERE symbol = @symbol + AND timestamp > @lookback +), +macd_line AS ( + SELECT + timestamp, + symbol, + close, + ema12, + ema26, + ema12 - ema26 AS macd + FROM ema +), +with_signal AS ( + SELECT + timestamp, + symbol, + close, + macd, + avg(macd, 'period', 9) OVER (PARTITION BY symbol ORDER BY timestamp) AS signal + FROM macd_line +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(macd, 6) AS macd, + round(signal, 6) AS signal, + round(macd - signal, 6) AS histogram +FROM with_signal +ORDER BY timestamp; +``` + +The query: +1. Calculates 12-period and 26-period EMAs using `avg(value, 'period', N)` +2. Computes MACD line as the difference: EMA12 - EMA26 +3. Calculates 9-period EMA of MACD as the signal line +4. Derives histogram as MACD - signal + +## Interpreting results + +- **MACD crosses above signal**: Bullish signal, momentum turning up +- **MACD crosses below signal**: Bearish signal, momentum turning down +- **Histogram growing**: Momentum strengthening in current direction +- **Histogram shrinking**: Momentum weakening, potential reversal +- **MACD above zero**: Uptrend (fast EMA above slow EMA) +- **MACD below zero**: Downtrend (fast EMA below slow EMA) + +:::note Standard parameters +The classic MACD uses 12/26/9 periods. Some traders adjust these: +- Faster signals: 8/17/9 +- Slower signals: 19/39/9 +::: + +:::info Related documentation +- [EMA window function](/docs/query/functions/window-functions/reference/#avg) +- [Window functions overview](/docs/query/functions/window-functions/overview/) +::: diff --git a/documentation/cookbook/sql/finance/maximum-drawdown.md b/documentation/cookbook/sql/finance/maximum-drawdown.md new file mode 100644 index 000000000..7ab6b5b7d --- /dev/null +++ b/documentation/cookbook/sql/finance/maximum-drawdown.md @@ -0,0 +1,96 @@ +--- +title: Maximum drawdown +sidebar_label: Maximum drawdown +description: Calculate maximum drawdown to measure the largest peak-to-trough decline for risk assessment +--- + +Maximum drawdown measures the largest percentage decline from a peak to a trough before a new peak is reached. It's a key risk metric showing the worst-case loss an investor would have experienced. + +## Problem + +You want to measure downside risk beyond simple volatility. Standard deviation treats up and down moves equally, but investors care more about losses. Maximum drawdown shows the actual worst decline experienced. + +## Solution + +```questdb-sql demo title="Calculate rolling maximum drawdown" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH with_peak AS ( + SELECT + timestamp, + symbol, + close, + max(close) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS running_peak + FROM market_data_ohlc_15m + WHERE symbol = @symbol + AND timestamp > @lookback +), +with_drawdown AS ( + SELECT + timestamp, + symbol, + close, + running_peak, + (close - running_peak) / running_peak * 100 AS drawdown + FROM with_peak +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(running_peak, 5) AS peak, + round(drawdown, 4) AS drawdown_pct, + round(min(drawdown) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ), 4) AS max_drawdown_pct +FROM with_drawdown +ORDER BY timestamp; +``` + +The query: +1. Tracks the running maximum (peak) price using `max() OVER (... UNBOUNDED PRECEDING)` +2. Calculates current drawdown as percentage from peak +3. Tracks the minimum (worst) drawdown seen so far + +## Interpreting results + +- **Drawdown = 0%**: At a new high +- **Drawdown negative**: Currently below peak by that percentage +- **Max drawdown**: Worst decline seen in the period +- **Recovery**: When drawdown returns to 0%, a new peak is reached + +## Finding drawdown periods + +```questdb-sql title="Identify significant drawdown periods" +DECLARE @symbol := 'EURUSD' + +WITH with_peak AS ( + SELECT timestamp, symbol, close, + max(close) OVER (PARTITION BY symbol ORDER BY timestamp ROWS UNBOUNDED PRECEDING) AS running_peak + FROM market_data_ohlc_15m + WHERE symbol = @symbol +), +with_drawdown AS ( + SELECT timestamp, symbol, close, running_peak, + (close - running_peak) / running_peak * 100 AS drawdown + FROM with_peak +) +SELECT timestamp, symbol, round(close, 5) AS close, round(drawdown, 2) AS drawdown_pct +FROM with_drawdown +WHERE drawdown < -1 -- Drawdowns greater than 1% +ORDER BY drawdown +LIMIT 10; +``` + +:::info Related documentation +- [max() window function](/docs/query/functions/window-functions/reference/#max) +- [min() window function](/docs/query/functions/window-functions/reference/#min) +::: diff --git a/documentation/cookbook/sql/finance/obv.md b/documentation/cookbook/sql/finance/obv.md new file mode 100644 index 000000000..640086d41 --- /dev/null +++ b/documentation/cookbook/sql/finance/obv.md @@ -0,0 +1,69 @@ +--- +title: OBV (On-Balance Volume) +sidebar_label: OBV +description: Calculate On-Balance Volume to track cumulative buying and selling pressure using volume flow +--- + +On-Balance Volume (OBV) is a cumulative indicator that adds volume on up days and subtracts volume on down days. It shows whether volume is flowing into or out of an asset, often leading price movements. + +## Problem + +You want to confirm price trends with volume or spot divergences where volume doesn't support price movement. Raw volume numbers don't show direction, and comparing volumes across different time periods is difficult. + +## Solution + +```questdb-sql demo title="Calculate On-Balance Volume" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH with_direction AS ( + SELECT + timestamp, + symbol, + close, + quantity AS volume, + CASE + WHEN close > lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) THEN quantity + WHEN close < lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) THEN -quantity + ELSE 0 + END AS directed_volume + FROM fx_trades_ohlc_1m + WHERE symbol = @symbol + AND timestamp > @lookback +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(volume, 0) AS volume, + round(sum(directed_volume) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ), 0) AS obv +FROM with_direction +ORDER BY timestamp; +``` + +The query: +1. Compares each close to the previous close +2. Assigns positive volume if price went up, negative if down, zero if unchanged +3. Calculates cumulative sum of directed volume + +## Interpreting results + +- **OBV rising with price**: Uptrend confirmed by volume +- **OBV falling with price**: Downtrend confirmed by volume +- **OBV rising, price flat**: Accumulation, potential breakout up +- **OBV falling, price flat**: Distribution, potential breakout down +- **OBV divergence from price**: Trend may be weakening + +:::note OBV absolute value +The absolute value of OBV is meaningless. What matters is the direction and whether it confirms or diverges from price. +::: + +:::info Related documentation +- [sum() window function](/docs/query/functions/window-functions/reference/#sum) +- [lag() function](/docs/query/functions/window-functions/reference/#lag) +::: diff --git a/documentation/cookbook/sql/finance/rate-of-change.md b/documentation/cookbook/sql/finance/rate-of-change.md new file mode 100644 index 000000000..87794f63b --- /dev/null +++ b/documentation/cookbook/sql/finance/rate-of-change.md @@ -0,0 +1,55 @@ +--- +title: Rate of Change (ROC) +sidebar_label: Rate of Change +description: Calculate Rate of Change momentum indicator to measure percentage price change over a period +--- + +Rate of Change (ROC) measures the percentage change in price between the current price and the price N periods ago. It oscillates around zero, with positive values indicating upward momentum and negative values indicating downward momentum. + +## Problem + +You want a simple momentum indicator that shows how fast price is changing. Raw price differences don't account for the price level, making comparison across assets difficult. + +## Solution + +```questdb-sql demo title="Calculate 12-period Rate of Change" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(lag(close, 12) OVER (PARTITION BY symbol ORDER BY timestamp), 5) AS close_12_ago, + round( + (close - lag(close, 12) OVER (PARTITION BY symbol ORDER BY timestamp)) / + lag(close, 12) OVER (PARTITION BY symbol ORDER BY timestamp) * 100, + 4 + ) AS roc +FROM market_data_ohlc_15m +WHERE symbol = @symbol + AND timestamp > @lookback +ORDER BY timestamp; +``` + +The formula: `ROC = ((Close - Close N periods ago) / Close N periods ago) × 100` + +## Interpreting results + +- **ROC > 0**: Price higher than N periods ago, upward momentum +- **ROC < 0**: Price lower than N periods ago, downward momentum +- **ROC crossing zero**: Potential trend change +- **Extreme ROC values**: Overbought/oversold, may revert to mean + +:::note Period selection +Common ROC periods: +- **9 or 12**: Short-term momentum +- **25**: Medium-term (roughly one month of daily data) +- **200**: Long-term trend +::: + +:::info Related documentation +- [lag() function](/docs/query/functions/window-functions/reference/#lag) +- [Window functions overview](/docs/query/functions/window-functions/overview/) +::: diff --git a/documentation/cookbook/sql/finance/realized-volatility.md b/documentation/cookbook/sql/finance/realized-volatility.md new file mode 100644 index 000000000..b14823148 --- /dev/null +++ b/documentation/cookbook/sql/finance/realized-volatility.md @@ -0,0 +1,80 @@ +--- +title: Realized volatility +sidebar_label: Realized volatility +description: Calculate realized volatility from historical returns for risk measurement and comparison to implied volatility +--- + +Realized volatility measures the actual historical volatility of returns over a period. It's typically annualized to allow comparison with implied volatility from options markets. + +## Problem + +You want to measure how volatile an asset has actually been, either for risk management or to compare with implied volatility. ATR measures price range but not the statistical dispersion of returns. + +## Solution + +```questdb-sql demo title="Calculate 20-period realized volatility (annualized)" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH returns AS ( + SELECT + timestamp, + symbol, + close, + ln(close / lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)) AS log_return + FROM market_data_ohlc_15m + WHERE symbol = @symbol + AND timestamp > @lookback +), +with_stats AS ( + SELECT + timestamp, + symbol, + close, + log_return, + avg(log_return) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 19 PRECEDING AND CURRENT ROW + ) AS mean_return, + avg(log_return * log_return) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 19 PRECEDING AND CURRENT ROW + ) AS mean_sq_return + FROM returns + WHERE log_return IS NOT NULL +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(log_return * 100, 4) AS return_pct, + round(sqrt(mean_sq_return - mean_return * mean_return) * sqrt(252 * 96) * 100, 2) AS realized_vol_annualized +FROM with_stats +ORDER BY timestamp; +``` + +The query: +1. Calculates log returns: `ln(close / previous_close)` +2. Computes rolling standard deviation using variance formula +3. Annualizes by multiplying by `sqrt(trading_periods_per_year)` (252 days × 96 fifteen-minute periods = 24,192) + +## Interpreting results + +- **High realized vol**: Market has been volatile, expect continued movement +- **Low realized vol**: Market has been calm, potential for breakout +- **Realized > Implied**: Options may be cheap (if you expect volatility to continue) +- **Realized < Implied**: Options may be expensive + +:::note Annualization factor +For 15-minute data with 24/7 trading: `sqrt(365 * 96) ≈ 187` +For daily data with ~252 trading days: `sqrt(252) ≈ 15.87` +::: + +:::info Related documentation +- [Rolling standard deviation recipe](/docs/cookbook/sql/finance/rolling-stddev/) +- [Window functions](/docs/query/functions/window-functions/overview/) +- [ln() function](/docs/query/functions/numeric/#ln) +::: diff --git a/documentation/cookbook/sql/finance/rsi.md b/documentation/cookbook/sql/finance/rsi.md new file mode 100644 index 000000000..6dc62409e --- /dev/null +++ b/documentation/cookbook/sql/finance/rsi.md @@ -0,0 +1,79 @@ +--- +title: RSI (Relative Strength Index) +sidebar_label: RSI +description: Calculate the Relative Strength Index momentum oscillator to identify overbought and oversold conditions +--- + +The Relative Strength Index (RSI) is a momentum oscillator that measures the speed and magnitude of recent price changes to evaluate overbought or oversold conditions. RSI oscillates between 0 and 100, with readings above 70 typically indicating overbought conditions and below 30 indicating oversold. + +## Problem + +You want to identify when an asset may be overbought or oversold based on recent price momentum. Raw price changes don't account for the relative strength of up moves versus down moves over a lookback period. + +## Solution + +```questdb-sql demo title="Calculate 14-period RSI with EMA smoothing" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH changes AS ( + SELECT + timestamp, + symbol, + close, + close - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) AS change + FROM market_data_ohlc_15m + WHERE symbol = @symbol + AND timestamp > @lookback +), +gains_losses AS ( + SELECT + timestamp, + symbol, + close, + CASE WHEN change > 0 THEN change ELSE 0 END AS gain, + CASE WHEN change < 0 THEN -change ELSE 0 END AS loss + FROM changes +), +smoothed AS ( + SELECT + timestamp, + symbol, + close, + avg(gain, 'period', 14) OVER (PARTITION BY symbol ORDER BY timestamp) AS avg_gain, + avg(loss, 'period', 14) OVER (PARTITION BY symbol ORDER BY timestamp) AS avg_loss + FROM gains_losses +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(100 - (100 / (1 + avg_gain / avg_loss)), 2) AS rsi +FROM smoothed +WHERE avg_loss > 0 +ORDER BY timestamp; +``` + +The query: +1. Calculates price changes using `lag()` +2. Separates gains (positive changes) and losses (negative changes) +3. Applies 14-period EMA smoothing to both using `avg(value, 'period', N)` +4. Computes RSI as `100 - (100 / (1 + avg_gain / avg_loss))` + +## Interpreting results + +- **RSI > 70**: Overbought, price may be due for a pullback +- **RSI < 30**: Oversold, price may be due for a bounce +- **RSI = 50**: Neutral momentum +- **Divergence**: When price makes new highs but RSI doesn't, it may signal weakening momentum + +:::note RSI smoothing +Traditional RSI uses Wilder's smoothing (equivalent to EMA with period 2N-1). The `avg(value, 'period', 14)` function uses standard EMA where α = 2/(14+1). For exact Wilder smoothing, use `avg(value, 'period', 27)` for a 14-period RSI. +::: + +:::info Related documentation +- [Window functions](/docs/query/functions/window-functions/overview/) +- [EMA window function](/docs/query/functions/window-functions/reference/#avg) +- [lag() function](/docs/query/functions/window-functions/reference/#lag) +::: diff --git a/documentation/cookbook/sql/finance/stochastic.md b/documentation/cookbook/sql/finance/stochastic.md new file mode 100644 index 000000000..52eac7ee0 --- /dev/null +++ b/documentation/cookbook/sql/finance/stochastic.md @@ -0,0 +1,82 @@ +--- +title: Stochastic Oscillator +sidebar_label: Stochastic Oscillator +description: Calculate the Stochastic Oscillator to identify overbought and oversold conditions based on price position within recent range +--- + +The Stochastic Oscillator compares a closing price to its price range over a period. It generates values between 0 and 100, showing where the current close sits relative to recent highs and lows. + +## Problem + +You want to identify overbought and oversold conditions based on where price is trading within its recent range. Unlike RSI which measures momentum, Stochastic shows the position of price relative to its high-low range. + +## Solution + +```questdb-sql demo title="Calculate Stochastic %K and %D" +DECLARE + @symbol := 'EURUSD', + @lookback := dateadd('M', -1, now()) + +WITH ranges AS ( + SELECT + timestamp, + symbol, + close, + min(low) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 13 PRECEDING AND CURRENT ROW + ) AS lowest_low, + max(high) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 13 PRECEDING AND CURRENT ROW + ) AS highest_high + FROM market_data_ohlc_15m + WHERE symbol = @symbol + AND timestamp > @lookback +), +with_k AS ( + SELECT + timestamp, + symbol, + close, + (close - lowest_low) / (highest_high - lowest_low) * 100 AS pct_k + FROM ranges + WHERE highest_high > lowest_low +) +SELECT + timestamp, + symbol, + round(close, 5) AS close, + round(pct_k, 2) AS pct_k, + round(avg(pct_k) OVER ( + PARTITION BY symbol + ORDER BY timestamp + ROWS BETWEEN 2 PRECEDING AND CURRENT ROW + ), 2) AS pct_d +FROM with_k +ORDER BY timestamp; +``` + +The query: +1. Calculates 14-period lowest low and highest high using window functions +2. Computes %K as `(close - lowest_low) / (highest_high - lowest_low) * 100` +3. Calculates %D as 3-period SMA of %K + +## Interpreting results + +- **%K > 80**: Overbought zone +- **%K < 20**: Oversold zone +- **%K crosses above %D**: Bullish signal +- **%K crosses below %D**: Bearish signal +- **Divergence**: Price makes new high but %K doesn't, signals potential reversal + +:::note Slow vs Fast Stochastic +This is the "slow" stochastic where %K is already smoothed by using a 3-period %D. The "fast" stochastic uses raw %K values, which are noisier. +::: + +:::info Related documentation +- [Window functions](/docs/query/functions/window-functions/overview/) +- [min/max window functions](/docs/query/functions/window-functions/reference/#min) +::: diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 020a9c6f2..25bdc9240 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -745,19 +745,47 @@ module.exports = { type: "category", label: "Capital Markets", collapsed: true, + link: { + type: "doc", + id: "cookbook/sql/finance/index", + }, items: [ - "cookbook/sql/finance/compound-interest", - "cookbook/sql/finance/cumulative-product", + { + type: "doc", + id: "cookbook/sql/finance/index", + label: "Overview", + }, + // Price-Based Indicators + "cookbook/sql/finance/ohlc", "cookbook/sql/finance/vwap", "cookbook/sql/finance/bollinger-bands", "cookbook/sql/finance/bollinger-bandwidth", - "cookbook/sql/finance/tick-trin", - "cookbook/sql/finance/aggressor-volume-imbalance", + // Momentum Indicators + "cookbook/sql/finance/rsi", + "cookbook/sql/finance/macd", + "cookbook/sql/finance/stochastic", + "cookbook/sql/finance/rate-of-change", + // Volatility Indicators + "cookbook/sql/finance/atr", + "cookbook/sql/finance/rolling-stddev", + "cookbook/sql/finance/donchian-channels", + "cookbook/sql/finance/keltner-channels", + "cookbook/sql/finance/realized-volatility", + // Volume & Order Flow + "cookbook/sql/finance/obv", "cookbook/sql/finance/volume-profile", "cookbook/sql/finance/volume-spike", - "cookbook/sql/finance/rolling-stddev", + "cookbook/sql/finance/aggressor-volume-imbalance", + // Risk Metrics + "cookbook/sql/finance/maximum-drawdown", + // Market Microstructure + "cookbook/sql/finance/bid-ask-spread", "cookbook/sql/finance/liquidity-comparison", - "cookbook/sql/finance/ohlc", + // Market Breadth + "cookbook/sql/finance/tick-trin", + // Math Utilities + "cookbook/sql/finance/compound-interest", + "cookbook/sql/finance/cumulative-product", ], }, { From d0ee55e803a38c6e14a6080b32b711f3815fe8f7 Mon Sep 17 00:00:00 2001 From: javier Date: Thu, 29 Jan 2026 19:42:39 +0100 Subject: [PATCH 2/2] Update financial indicator recipes to use TICK syntax MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace legacy dateadd patterns with TICK syntax in 11 recipes: - @lookback := dateadd('M', -1, now()) → @lookback := '$now - 1M..$now' - timestamp > @lookback → timestamp IN @lookback Updated: RSI, MACD, ATR, Stochastic, Donchian Channels, Keltner Channels, OBV, Rate of Change, Realized Volatility, Maximum Drawdown, Bid-Ask Spread Also adds elapsed-time recipe to sidebar. --- documentation/cookbook/sql/finance/atr.md | 8 ++++++-- documentation/cookbook/sql/finance/bid-ask-spread.md | 8 ++++---- .../cookbook/sql/finance/donchian-channels.md | 4 ++-- .../cookbook/sql/finance/keltner-channels.md | 10 +++++----- documentation/cookbook/sql/finance/macd.md | 4 ++-- .../cookbook/sql/finance/maximum-drawdown.md | 4 ++-- documentation/cookbook/sql/finance/obv.md | 10 +++++----- documentation/cookbook/sql/finance/rate-of-change.md | 4 ++-- .../cookbook/sql/finance/realized-volatility.md | 11 +++++------ documentation/cookbook/sql/finance/rsi.md | 8 ++++---- documentation/cookbook/sql/finance/stochastic.md | 8 ++++---- documentation/sidebars.js | 1 + 12 files changed, 42 insertions(+), 38 deletions(-) diff --git a/documentation/cookbook/sql/finance/atr.md b/documentation/cookbook/sql/finance/atr.md index 27d4d77c6..991f86e28 100644 --- a/documentation/cookbook/sql/finance/atr.md +++ b/documentation/cookbook/sql/finance/atr.md @@ -15,7 +15,7 @@ You want to measure volatility to set appropriate stop-losses or position sizes. ```questdb-sql demo title="Calculate 14-period ATR" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH with_prev AS ( SELECT @@ -27,7 +27,7 @@ WITH with_prev AS ( lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) AS prev_close FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ), true_range AS ( SELECT @@ -83,6 +83,10 @@ entry_price - 2 * atr AS stop_loss (account_size * 0.01) / atr AS position_size ``` +:::note EMA vs Wilder's smoothing +This recipe uses standard EMA smoothing via `avg(value, 'period', 14)` where α = 2/(N+1). Wilder's original ATR uses α = 1/N, which is more gradual. For exact Wilder smoothing with a 14-period lookback, use `avg(value, 'period', 27)`. Most modern platforms offer both variants. +::: + :::info Related documentation - [EMA window function](/docs/query/functions/window-functions/reference/#avg) - [lag() function](/docs/query/functions/window-functions/reference/#lag) diff --git a/documentation/cookbook/sql/finance/bid-ask-spread.md b/documentation/cookbook/sql/finance/bid-ask-spread.md index 0fd606403..afc5f6c88 100644 --- a/documentation/cookbook/sql/finance/bid-ask-spread.md +++ b/documentation/cookbook/sql/finance/bid-ask-spread.md @@ -15,7 +15,7 @@ You want to measure market liquidity and transaction costs. Narrow spreads indic ```questdb-sql demo title="Calculate bid-ask spread metrics" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('h', -1, now()) + @lookback := '$now - 1h..$now' SELECT timestamp, @@ -27,7 +27,7 @@ SELECT round((bid_price + ask_price) / 2, 5) AS mid_price FROM core_price WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ORDER BY timestamp; ``` @@ -41,7 +41,7 @@ The query calculates: ```questdb-sql demo title="Average spread by time period" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('d', -1, now()) + @lookback := '$now - 1d..$now' SELECT timestamp, @@ -52,7 +52,7 @@ SELECT count() AS quote_count FROM core_price WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback SAMPLE BY 1h ORDER BY timestamp; ``` diff --git a/documentation/cookbook/sql/finance/donchian-channels.md b/documentation/cookbook/sql/finance/donchian-channels.md index 74c35c211..3f55dd0dc 100644 --- a/documentation/cookbook/sql/finance/donchian-channels.md +++ b/documentation/cookbook/sql/finance/donchian-channels.md @@ -15,7 +15,7 @@ You want to identify breakout levels and trading ranges. Moving averages smooth ```questdb-sql demo title="Calculate 20-period Donchian Channels" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' SELECT timestamp, @@ -42,7 +42,7 @@ SELECT )) / 2, 5) AS middle_channel FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ORDER BY timestamp; ``` diff --git a/documentation/cookbook/sql/finance/keltner-channels.md b/documentation/cookbook/sql/finance/keltner-channels.md index 99eab4406..82fd43cdb 100644 --- a/documentation/cookbook/sql/finance/keltner-channels.md +++ b/documentation/cookbook/sql/finance/keltner-channels.md @@ -12,10 +12,10 @@ You want volatility bands that adapt to market conditions but are smoother than ## Solution -```questdb-sql demo title="Calculate Keltner Channels with 20 EMA and 2x ATR" +```questdb-sql demo title="Calculate Keltner Channels (20-period EMA ± 2× ATR)" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH with_prev AS ( SELECT @@ -27,7 +27,7 @@ WITH with_prev AS ( lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) AS prev_close FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ), with_tr AS ( SELECT @@ -50,7 +50,7 @@ with_indicators AS ( symbol, close, avg(close, 'period', 20) OVER (PARTITION BY symbol ORDER BY timestamp) AS ema20, - avg(tr, 'period', 10) OVER (PARTITION BY symbol ORDER BY timestamp) AS atr + avg(tr, 'period', 20) OVER (PARTITION BY symbol ORDER BY timestamp) AS atr FROM with_tr ) SELECT @@ -66,7 +66,7 @@ ORDER BY timestamp; The query: 1. Calculates True Range accounting for gaps -2. Applies EMA smoothing to both price (20-period) and ATR (10-period) +2. Applies 20-period EMA smoothing to both price and ATR 3. Creates bands at ±2 ATR from the EMA ## Interpreting results diff --git a/documentation/cookbook/sql/finance/macd.md b/documentation/cookbook/sql/finance/macd.md index af14e049d..62882a2e6 100644 --- a/documentation/cookbook/sql/finance/macd.md +++ b/documentation/cookbook/sql/finance/macd.md @@ -15,7 +15,7 @@ You want to identify trend changes and momentum shifts. Simple moving averages l ```questdb-sql demo title="Calculate MACD with signal line and histogram" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH ema AS ( SELECT @@ -26,7 +26,7 @@ WITH ema AS ( avg(close, 'period', 26) OVER (PARTITION BY symbol ORDER BY timestamp) AS ema26 FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ), macd_line AS ( SELECT diff --git a/documentation/cookbook/sql/finance/maximum-drawdown.md b/documentation/cookbook/sql/finance/maximum-drawdown.md index 7ab6b5b7d..15b622de9 100644 --- a/documentation/cookbook/sql/finance/maximum-drawdown.md +++ b/documentation/cookbook/sql/finance/maximum-drawdown.md @@ -15,7 +15,7 @@ You want to measure downside risk beyond simple volatility. Standard deviation t ```questdb-sql demo title="Calculate rolling maximum drawdown" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH with_peak AS ( SELECT @@ -29,7 +29,7 @@ WITH with_peak AS ( ) AS running_peak FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ), with_drawdown AS ( SELECT diff --git a/documentation/cookbook/sql/finance/obv.md b/documentation/cookbook/sql/finance/obv.md index 640086d41..16659f016 100644 --- a/documentation/cookbook/sql/finance/obv.md +++ b/documentation/cookbook/sql/finance/obv.md @@ -15,22 +15,22 @@ You want to confirm price trends with volume or spot divergences where volume do ```questdb-sql demo title="Calculate On-Balance Volume" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH with_direction AS ( SELECT timestamp, symbol, close, - quantity AS volume, + total_volume AS volume, CASE - WHEN close > lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) THEN quantity - WHEN close < lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) THEN -quantity + WHEN close > lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) THEN total_volume + WHEN close < lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) THEN -total_volume ELSE 0 END AS directed_volume FROM fx_trades_ohlc_1m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ) SELECT timestamp, diff --git a/documentation/cookbook/sql/finance/rate-of-change.md b/documentation/cookbook/sql/finance/rate-of-change.md index 87794f63b..9c6ec843b 100644 --- a/documentation/cookbook/sql/finance/rate-of-change.md +++ b/documentation/cookbook/sql/finance/rate-of-change.md @@ -15,7 +15,7 @@ You want a simple momentum indicator that shows how fast price is changing. Raw ```questdb-sql demo title="Calculate 12-period Rate of Change" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' SELECT timestamp, @@ -29,7 +29,7 @@ SELECT ) AS roc FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ORDER BY timestamp; ``` diff --git a/documentation/cookbook/sql/finance/realized-volatility.md b/documentation/cookbook/sql/finance/realized-volatility.md index b14823148..b81a5e950 100644 --- a/documentation/cookbook/sql/finance/realized-volatility.md +++ b/documentation/cookbook/sql/finance/realized-volatility.md @@ -15,7 +15,7 @@ You want to measure how volatile an asset has actually been, either for risk man ```questdb-sql demo title="Calculate 20-period realized volatility (annualized)" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH returns AS ( SELECT @@ -25,7 +25,7 @@ WITH returns AS ( ln(close / lag(close) OVER (PARTITION BY symbol ORDER BY timestamp)) AS log_return FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ), with_stats AS ( SELECT @@ -51,7 +51,7 @@ SELECT symbol, round(close, 5) AS close, round(log_return * 100, 4) AS return_pct, - round(sqrt(mean_sq_return - mean_return * mean_return) * sqrt(252 * 96) * 100, 2) AS realized_vol_annualized + round(sqrt(mean_sq_return - mean_return * mean_return) * sqrt(365 * 96) * 100, 2) AS realized_vol_annualized FROM with_stats ORDER BY timestamp; ``` @@ -59,7 +59,7 @@ ORDER BY timestamp; The query: 1. Calculates log returns: `ln(close / previous_close)` 2. Computes rolling standard deviation using variance formula -3. Annualizes by multiplying by `sqrt(trading_periods_per_year)` (252 days × 96 fifteen-minute periods = 24,192) +3. Annualizes by multiplying by `sqrt(periods_per_year)` (365 days × 96 fifteen-minute periods = 35,040 for the 24/7 simulated data) ## Interpreting results @@ -69,8 +69,7 @@ The query: - **Realized < Implied**: Options may be expensive :::note Annualization factor -For 15-minute data with 24/7 trading: `sqrt(365 * 96) ≈ 187` -For daily data with ~252 trading days: `sqrt(252) ≈ 15.87` +The demo FX data is simulated continuously (24/7, including weekends), so the annualization factor uses `365 * 96` (365 days × 96 fifteen-minute periods per day). For real FX markets (24/5), use `252 * 96`. For daily data, use `sqrt(252) ≈ 15.87`. ::: :::info Related documentation diff --git a/documentation/cookbook/sql/finance/rsi.md b/documentation/cookbook/sql/finance/rsi.md index 6dc62409e..829588590 100644 --- a/documentation/cookbook/sql/finance/rsi.md +++ b/documentation/cookbook/sql/finance/rsi.md @@ -15,7 +15,7 @@ You want to identify when an asset may be overbought or oversold based on recent ```questdb-sql demo title="Calculate 14-period RSI with EMA smoothing" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH changes AS ( SELECT @@ -25,7 +25,7 @@ WITH changes AS ( close - lag(close) OVER (PARTITION BY symbol ORDER BY timestamp) AS change FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ), gains_losses AS ( SELECT @@ -68,8 +68,8 @@ The query: - **RSI = 50**: Neutral momentum - **Divergence**: When price makes new highs but RSI doesn't, it may signal weakening momentum -:::note RSI smoothing -Traditional RSI uses Wilder's smoothing (equivalent to EMA with period 2N-1). The `avg(value, 'period', 14)` function uses standard EMA where α = 2/(14+1). For exact Wilder smoothing, use `avg(value, 'period', 27)` for a 14-period RSI. +:::note EMA vs Wilder's smoothing +This recipe uses standard EMA smoothing via `avg(value, 'period', 14)` where α = 2/(N+1). Traditional RSI (Wilder's) uses α = 1/N, which is more gradual. For exact Wilder smoothing with a 14-period lookback, use `avg(value, 'period', 27)`. Most modern platforms offer both variants. ::: :::info Related documentation diff --git a/documentation/cookbook/sql/finance/stochastic.md b/documentation/cookbook/sql/finance/stochastic.md index 52eac7ee0..d529cce6b 100644 --- a/documentation/cookbook/sql/finance/stochastic.md +++ b/documentation/cookbook/sql/finance/stochastic.md @@ -15,7 +15,7 @@ You want to identify overbought and oversold conditions based on where price is ```questdb-sql demo title="Calculate Stochastic %K and %D" DECLARE @symbol := 'EURUSD', - @lookback := dateadd('M', -1, now()) + @lookback := '$now - 1M..$now' WITH ranges AS ( SELECT @@ -34,7 +34,7 @@ WITH ranges AS ( ) AS highest_high FROM market_data_ohlc_15m WHERE symbol = @symbol - AND timestamp > @lookback + AND timestamp IN @lookback ), with_k AS ( SELECT @@ -72,8 +72,8 @@ The query: - **%K crosses below %D**: Bearish signal - **Divergence**: Price makes new high but %K doesn't, signals potential reversal -:::note Slow vs Fast Stochastic -This is the "slow" stochastic where %K is already smoothed by using a 3-period %D. The "fast" stochastic uses raw %K values, which are noisier. +:::note Fast vs Slow Stochastic +This recipe shows the **Fast Stochastic** (raw %K with 3-period %D). For the **Slow Stochastic**, you would first smooth %K with a 3-period SMA, then apply another 3-period SMA for %D. The Slow version is less noisy but more lagging. ::: :::info Related documentation diff --git a/documentation/sidebars.js b/documentation/sidebars.js index 25bdc9240..b29fda721 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -793,6 +793,7 @@ module.exports = { label: "Time-Series Patterns", collapsed: true, items: [ + "cookbook/sql/time-series/elapsed-time", "cookbook/sql/time-series/force-designated-timestamp", "cookbook/sql/time-series/latest-n-per-partition", "cookbook/sql/time-series/session-windows",