{"slug":"do-volume-indicators-predict-anything","qid":"grid","label":"grid","post_title":"do-volume-indicators-predict-anything","post_url":"/blog/do-volume-indicators-predict-anything#q-grid","columns":["label","obv_gap_pct","vpt_gap_pct","obv_signal_count","vpt_signal_count"],"rows":[{"label":"10-session lookback","obv_gap_pct":0.23,"vpt_gap_pct":-0.18,"obv_signal_count":734,"vpt_signal_count":497},{"label":"20-session lookback","obv_gap_pct":1.04,"vpt_gap_pct":0.18,"obv_signal_count":512,"vpt_signal_count":338},{"label":"40-session lookback","obv_gap_pct":1.63,"vpt_gap_pct":1.17,"obv_signal_count":407,"vpt_signal_count":305},{"label":"60-session lookback","obv_gap_pct":0.91,"vpt_gap_pct":0.14,"obv_signal_count":309,"vpt_signal_count":305}],"shape":"table","sql":"WITH\n    bars AS\n    (\n        SELECT\n            ticker,\n            date,\n            max(toFloat64(close))  AS close,\n            max(toFloat64(volume)) AS volume\n        FROM global_markets.stocks_daily_aggs\n        WHERE ticker IN ('MSFT', 'SPY', 'KO', 'JNJ', 'JPM', 'XOM', 'PG', 'PEP', 'MCD', 'HD')\n          AND date >= '2016-01-04'\n          AND date <= '2026-06-30'\n        GROUP BY ticker, date\n    ),\n    stepped AS\n    (\n        SELECT\n            ticker,\n            date,\n            close,\n            volume,\n            lagInFrame(close, 1) OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_close\n        FROM bars\n    ),\n    cumulative AS\n    (\n        SELECT\n            ticker,\n            date,\n            close,\n            sum(if(prev_close = 0, 0, if(close > prev_close, volume, if(close < prev_close, -volume, 0))))\n                OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS obv,\n            sum(if(prev_close = 0, 0, volume * (close / prev_close - 1)))\n                OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS vpt,\n            row_number() OVER (PARTITION BY ticker ORDER BY date ASC) AS bar_no\n        FROM stepped\n    ),\n    marked AS\n    (\n        SELECT\n            close,\n            obv,\n            vpt,\n            bar_no,\n            max(close)             OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN  9 PRECEDING AND CURRENT ROW) AS high_10,\n            max(close)             OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 19 PRECEDING AND CURRENT ROW) AS high_20,\n            max(close)             OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 39 PRECEDING AND CURRENT ROW) AS high_40,\n            max(close)             OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 59 PRECEDING AND CURRENT ROW) AS high_60,\n            lagInFrame(obv, 10)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 10 PRECEDING AND CURRENT ROW) AS obv_10_back,\n            lagInFrame(obv, 20)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS obv_20_back,\n            lagInFrame(obv, 40)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 40 PRECEDING AND CURRENT ROW) AS obv_40_back,\n            lagInFrame(obv, 60)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 60 PRECEDING AND CURRENT ROW) AS obv_60_back,\n            lagInFrame(vpt, 10)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 10 PRECEDING AND CURRENT ROW) AS vpt_10_back,\n            lagInFrame(vpt, 20)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 20 PRECEDING AND CURRENT ROW) AS vpt_20_back,\n            lagInFrame(vpt, 40)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 40 PRECEDING AND CURRENT ROW) AS vpt_40_back,\n            lagInFrame(vpt, 60)    OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN 60 PRECEDING AND CURRENT ROW) AS vpt_60_back,\n            leadInFrame(close, 20) OVER (PARTITION BY ticker ORDER BY date ASC ROWS BETWEEN CURRENT ROW AND 20 FOLLOWING) AS close_fwd_20\n        FROM cumulative\n    ),\n    specs AS\n    (\n        SELECT\n            ret,\n            spec.1 AS label,\n            spec.2 AS at_new_high,\n            spec.3 AS obv_divergent,\n            spec.4 AS vpt_divergent\n        FROM\n        (\n            SELECT\n                100 * (close_fwd_20 / close - 1) AS ret,\n                arrayJoin([\n                    ('10-session lookback', close >= high_10, obv < obv_10_back, vpt < vpt_10_back),\n                    ('20-session lookback', close >= high_20, obv < obv_20_back, vpt < vpt_20_back),\n                    ('40-session lookback', close >= high_40, obv < obv_40_back, vpt < vpt_40_back),\n                    ('60-session lookback', close >= high_60, obv < obv_60_back, vpt < vpt_60_back)\n                ]) AS spec\n            FROM marked\n            WHERE bar_no > 61\n              AND close_fwd_20 > 0\n        )\n    )\nSELECT\n    label,\n    round(avgIf(ret, at_new_high AND obv_divergent) - avgIf(ret, at_new_high AND NOT obv_divergent), 2) AS obv_gap_pct,\n    round(avgIf(ret, at_new_high AND vpt_divergent) - avgIf(ret, at_new_high AND NOT vpt_divergent), 2) AS vpt_gap_pct,\n    countIf(at_new_high AND obv_divergent) AS obv_signal_count,\n    countIf(at_new_high AND vpt_divergent) AS vpt_signal_count\nFROM specs\nGROUP BY label\nHAVING obv_signal_count > 50\n   AND vpt_signal_count > 50\n   AND countIf(at_new_high AND NOT obv_divergent) > 50\n   AND countIf(at_new_high AND NOT vpt_divergent) > 50\nORDER BY toUInt16OrZero(splitByChar('-', label)[1]) ASC","computed_at":"2026-10-01T16:15:09.436306+00:00","elapsed":1.787520431}