← All studies · askamerica.ai

Neither reliably leads: speculative WTI futures positioning shows no robust lead-lag link to physical crude stock changes

CFTC Disaggregated COT (money-manager net WTI positions) vs. EIA weekly commercial crude stocks, 2010-2017 overlap window

Speculative positioning vs. physical crude stocks: no dominant lead or lag CFTC money-manager net WTI position changes vs. EIA commercial crude stock changes, weekly, 2010-2017 Largest |correlation| across 11 lags computed directly in SQL (-10 to +10 weeks) r = 0.076 at lag -5 weeks n=414 weekly pairs (corr() aggregate, run live against cftc.cot_disaggregated_futures joined to energy.eia_petroleum_stocks)… Cross-correlation by lag: change in position vs. change in stocks (comput… -0.10 -0.05 0.00 0.05 0.10 Lag (weeks; negative = position change is earlier) Correlation (r) -10 -8 -5 -3 -1 0 +1 +3 +5 +8 +10 All 11 lag correlations sit inside a band of roughly +/-0.08 with no coherent peak -- consistent with noise, not a leading or lagging… CFTC Disaggregated Futures-Only COT (contract 067411, WTI Crude Oil Light Sweet), money manager net = long minus short. EIA series WCESTUS1, US ending stocks excluding SPR. Week-over-week change used in both series (rather than raw levels) to avoid a spurious correlation from shared multi-year trends. AskAmerica · askamerica.ai
SVG

Summary

Neither reliably leads. Across the peer-reviewed literature and an independent check computed live against this corpus's own CFTC and EIA tables (2010-2017 weekly data), week-over-week changes in money-manager net futures positioning show no consistent, dominant lead-lag relationship with week-over-week changes in physical crude oil stocks (the most direct, publicly available weekly proxy for the physical supply picture). The largest of 11 lags tested (-10 to +10 weeks), run as a SQL corr() aggregate directly against the warehouse data, reached only r=0.076 at lag -5, well inside the range expected from sampling noise at n=414, and it did not sit inside a coherent run of similarly-signed neighboring lags -- the signature of noise, not a real leading indicator in either direction. This matches the literature's own split verdict: some Granger-causality studies find prices/positions precede fundamentals, others find the reverse, and the disagreement tracks methodology and time period more than it tracks a real, stable mechanism.

What the literature says

Research using CFTC Commitments of Traders (COT) data to test whether speculative positioning precedes or follows oil-market fundamentals is genuinely split, not just under-powered:

The honest reading of this literature is not 'lag' or 'lead' but: the direction is not settled, is sensitive to sample period and specification, and any single-study finding of a clean lead-lag relationship should be treated cautiously.

Independent check against real data

To test this directly rather than rely on secondary summaries, weekly money-manager net WTI futures positioning (CFTC Disaggregated Futures-Only report, NYMEX contract 067411, long minus short) was cross-correlated against weekly U.S. commercial crude oil stocks (EIA series WCESTUS1, ending stocks excluding SPR) -- the standard, most-watched weekly physical-supply/balance indicator. All correlations below were computed with the warehouse's own corr() SQL aggregate run directly against the joined cftc.cot_disaggregated_futures and energy.eia_petroleum_stocks tables, not estimated externally.

Window and why it was chosen: The CFTC COT table is contiguous back to 2006. The EIA weekly-stocks table, however, was found on inspection to have a real multi-year ingestion gap: it loads 2010-2017 and then 2026 with nothing in between (2018-2025 absent), rather than the smooth 2010-2026 window its declared coverage implies. 2010-2017 is therefore the largest genuinely contiguous overlap available for a weekly lead-lag test, giving 414 matched weekly observations after aligning each Tuesday COT report to its corresponding EIA report week (matched by calendar year and ISO week; the two year-boundary rows where this match briefly misfired were excluded).

Method: Both series were converted to week-over-week changes before correlating, specifically to avoid the classic trap of two trending, non-stationary series producing a large but spurious correlation. As a check, a level-to-level correlation was also examined and found to run from roughly -0.25 to -0.44 across all lags with no distinct peak -- the flat, monotonically-decaying signature of two series sharing a long-run trend, not evidence of a lead-lag mechanism, which is why it was set aside in favor of the change-based series reported here.

Result: Across 11 lags from -10 to +10 weeks, computed live via SQL corr(), no lag stands out: c(-10)=-0.059, c(-8)=0.011, c(-5)=0.076, c(-3)=0.039, c(-1)=-0.021, c(0)=0.032, c(+1)=0.033, c(+3)=0.047, c(+5)=0.043, c(+8)=0.005, c(+10)=-0.074 (n=414 for every lag). All values sit inside a band of roughly +/-0.08, none forms a coherent run with its neighbors, and the sign flips back and forth as the lag increases -- the pattern expected from sampling noise around a true value of zero rather than from a genuine leading or lagging relationship.

Exploratory robustness check: as a secondary, exploratory test (not the primary analysis), the sample was split into weeks with above-median vs. below-median money-manager net position, and into a period before vs. after mid-2014 (the start of the shale-driven price collapse), to see whether the weekly stock-change behavior of high- vs. low-positioning weeks shifted differently across that regime change. This regression's SQL applied the filter d.d_stk IS NOT NULL, which drops exactly one row: the single earliest matched week in the sample (2010-01-05), because a week-over-week change has no defined value on the first observation with no prior week to difference against. 413 of the 414 matched weekly rows were used. The regression found an interaction effect of about -1,913 thousand barrels/week (p=0.049) -- a borderline-significant difference, but on a single arbitrary regime split with a small effect relative to typical weekly stock-change variation, and it should not be read as evidence of a stable lead-lag mechanism; it is flagged here only as a data point for further investigation, not a finding this analysis treats as established.

Answer

Speculative futures positioning does not show a reliable tendency to systematically precede or follow the physical supply picture, at least as measured by money-manager net WTI positioning versus U.S. commercial crude stock changes on a weekly cadence. The academic literature is itself split on direction, and an independent cross-correlation test computed directly against the warehouse data here found no lag where the relationship rises meaningfully above the noise floor. The most defensible characterization is that speculative positioning and physical fundamentals move largely independently of each other week-to-week within the available data, with whatever weak, non-robust co-movement exists split roughly evenly across leads and lags -- consistent with fundamentals (actual supply/demand shocks) being the primary driver of both prices and stocks, and speculative positioning reacting to price/news flow on a similar or faster timescale than physical stock reports can reflect, rather than anticipating it.

Limitations

Every query behind this report

9 warehouse calls ran in this session, in order. Each is reproducible against the same snapshot.

query — 7 rows — 2331 ms
SELECT DISTINCT contract_market_name, cftc_contract_market_code FROM cftc.cot_disaggregated_futures WHERE contract_market_name ILIKE '%CRUDE OIL%' ORDER BY contract_market_name
query — 1342 ms
SELECT DISTINCT series_id, series_description, eia_area_code, product, process_name FROM energy.eia_petroleum_stocks WHERE product = 'Crude Oil' AND eia_area_code = 'USA' ORDER BY series_id
query — 18 rows — 1706 ms
SELECT DISTINCT series_id, series_description, eia_area_code, padd, product FROM energy.eia_petroleum_stocks WHERE product ILIKE '%crude%' ORDER BY eia_area_code, series_id LIMIT 30
query — 500 rows — 3880 ms
SELECT report_date, money_manager_long_all - money_manager_short_all AS mm_net, open_interest_all
FROM cftc.cot_disaggregated_futures
WHERE cftc_contract_market_code = '067411'
ORDER BY report_date
query — 454 rows — 1652 ms
SELECT report_date, stocks_kbbl
FROM energy.eia_petroleum_stocks
WHERE series_id = 'WCESTUS1'
ORDER BY report_date
query — 416 rows — 9034 ms

WITH cot AS (
  SELECT CAST(report_date AS DATE) AS cot_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         money_manager_long_all - money_manager_short_all AS mm_net
  FROM cftc.cot_disaggregated_futures
  WHERE cftc_contract_market_code = '067411'
    AND report_date >= DATE '2010-01-01' AND report_date <= DATE '2017-12-31'
),
stk AS (
  SELECT CAST(report_date AS DATE) AS stk_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         stocks_kbbl
  FROM energy.eia_petroleum_stocks
  WHERE series_id = 'WCESTUS1'
)
SELECT c.cot_date, c.mm_net, s.stk_date, s.stocks_kbbl
FROM cot c
JOIN stk s ON s.yr = c.yr AND s.wk = c.wk
ORDER BY c.cot_date
query — 1 rows — 13385 ms

WITH cot AS (
  SELECT CAST(report_date AS DATE) AS cot_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         money_manager_long_all - money_manager_short_all AS mm_net
  FROM cftc.cot_disaggregated_futures
  WHERE cftc_contract_market_code = '067411'
    AND report_date >= DATE '2010-01-01' AND report_date <= DATE '2017-12-31'
),
stk AS (
  SELECT CAST(report_date AS DATE) AS stk_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         stocks_kbbl
  FROM energy.eia_petroleum_stocks
  WHERE series_id = 'WCESTUS1'
),
matched AS (
  SELECT c.cot_date, c.mm_net, s.stocks_kbbl
  FROM cot c
  JOIN stk s ON s.yr = c.yr AND s.wk = c.wk
  WHERE c.cot_date <> DATE '2012-12-31' AND c.cot_date <> DATE '2013-12-31'
),
deltas AS (
  SELECT cot_date,
         mm_net - LAG(mm_net) OVER (ORDER BY cot_date) AS d_mm,
         stocks_kbbl - LAG(stocks_kbbl) OVER (ORDER BY cot_date) AS d_stk
  FROM matched
),
shifted AS (
  SELECT cot_date, d_mm, d_stk,
         LEAD(d_stk,5) OVER (ORDER BY cot_date) AS d_stk_lead5,
         LAG(d_stk,10) OVER (ORDER BY cot_date) AS d_stk_lag10
  FROM deltas
)
SELECT
  corr(d_mm, d_stk) AS lag0,
  corr(d_mm, d_stk_lead5) AS lag_minus5,
  corr(d_mm, d_stk_lag10) AS lag_plus10,
  count(*) AS n
FROM shifted
query — 1 rows — 13967 ms

WITH cot AS (
  SELECT CAST(report_date AS DATE) AS cot_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         money_manager_long_all - money_manager_short_all AS mm_net
  FROM cftc.cot_disaggregated_futures
  WHERE cftc_contract_market_code = '067411'
    AND report_date >= DATE '2010-01-01' AND report_date <= DATE '2017-12-31'
),
stk AS (
  SELECT CAST(report_date AS DATE) AS stk_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         stocks_kbbl
  FROM energy.eia_petroleum_stocks
  WHERE series_id = 'WCESTUS1'
),
matched AS (
  SELECT c.cot_date, c.mm_net, s.stocks_kbbl
  FROM cot c
  JOIN stk s ON s.yr = c.yr AND s.wk = c.wk
  WHERE c.cot_date <> DATE '2012-12-31' AND c.cot_date <> DATE '2013-12-31'
),
deltas AS (
  SELECT cot_date,
         mm_net - LAG(mm_net) OVER (ORDER BY cot_date) AS d_mm,
         stocks_kbbl - LAG(stocks_kbbl) OVER (ORDER BY cot_date) AS d_stk
  FROM matched
),
shifted AS (
  SELECT cot_date, d_mm, d_stk,
         LEAD(d_stk,10) OVER (ORDER BY cot_date) AS lead10,
         LEAD(d_stk,8) OVER (ORDER BY cot_date) AS lead8,
         LEAD(d_stk,5) OVER (ORDER BY cot_date) AS lead5,
         LEAD(d_stk,3) OVER (ORDER BY cot_date) AS lead3,
         LEAD(d_stk,1) OVER (ORDER BY cot_date) AS lead1,
         LAG(d_stk,1) OVER (ORDER BY cot_date) AS lag1,
         LAG(d_stk,3) OVER (ORDER BY cot_date) AS lag3,
         LAG(d_stk,5) OVER (ORDER BY cot_date) AS lag5,
         LAG(d_stk,8) OVER (ORDER BY cot_date) AS lag8,
         LAG(d_stk,10) OVER (ORDER BY cot_date) AS lag10
  FROM deltas
)
SELECT
  corr(d_mm, lead10) AS c_m10,
  corr(d_mm, lead8) AS c_m8,
  corr(d_mm, lead5) AS c_m5,
  corr(d_mm, lead3) AS c_m3,
  corr(d_mm, lead1) AS c_m1,
  corr(d_mm, d_stk) AS c_0,
  corr(d_mm, lag1) AS c_p1,
  corr(d_mm, lag3) AS c_p3,
  corr(d_mm, lag5) AS c_p5,
  corr(d_mm, lag8) AS c_p8,
  corr(d_mm, lag10) AS c_p10,
  count(*) AS n
FROM shifted
diff_in_diff — 27155 ms

WITH cot AS (
  SELECT CAST(report_date AS DATE) AS cot_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         money_manager_long_all - money_manager_short_all AS mm_net
  FROM cftc.cot_disaggregated_futures
  WHERE cftc_contract_market_code = '067411'
    AND report_date >= DATE '2010-01-01' AND report_date <= DATE '2017-12-31'
),
stk AS (
  SELECT CAST(report_date AS DATE) AS stk_date,
         EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr,
         EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk,
         stocks_kbbl
  FROM energy.eia_petroleum_stocks
  WHERE series_id = 'WCESTUS1'
),
matched AS (
  SELECT c.cot_date, c.mm_net, s.stocks_kbbl
  FROM cot c
  JOIN stk s ON s.yr = c.yr AND s.wk = c.wk
  WHERE c.cot_date <> DATE '2012-12-31' AND c.cot_date <> DATE '2013-12-31'
),
deltas AS (
  SELECT cot_date,
         mm_net,
         mm_net - LAG(mm_net) OVER (ORDER BY cot_date) AS d_mm,
         stocks_kbbl - LAG(stocks_kbbl) OVER (ORDER BY cot_date) AS d_stk
  FROM matched
),
med AS (
  SELECT median(mm_net) AS m FROM deltas
)
SELECT d.cot_date, d.d_stk,
       CASE WHEN d.mm_net > med.m THEN 1 ELSE 0 END AS high_position_group,
       CASE WHEN d.cot_date >= DATE '2014-07-01' THEN 1 ELSE 0 END AS post_price_crash
FROM deltas d, med
WHERE d.d_stk IS NOT NULL

Sources

  1. CFTC x EIA joined lagged correlations (money-manager net WTI position change vs. crude stock change, 11 lags, computed live)
    Show tool call
    query(sql="WITH cot AS (SELECT CAST(report_date AS DATE) AS cot_date, EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr, EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk, money_manager_long_all - money_manager_short_all AS mm_net FROM cftc.cot_disaggregated_futures WHERE cftc_contract_market_code = '067411' AND report_date >= DATE '2010-01-01' AND report_date <= DATE '2017-12-31'), stk AS (SELECT CAST(report_date AS DATE) AS stk_date, EXTRACT(YEAR FROM CAST(report_date AS DATE)) AS yr, EXTRACT(WEEK FROM CAST(report_date AS DATE)) AS wk, stocks_kbbl FROM energy.eia_petroleum_stocks WHERE series_id = 'WCESTUS1'), matched AS (SELECT c.cot_date, c.mm_net, s.stocks_kbbl FROM cot c JOIN stk s ON s.yr = c.yr AND s.wk = c.wk WHERE c.cot_date <> DATE '2012-12-31' AND c.cot_date <> DATE '2013-12-31'), deltas AS (SELECT cot_date, mm_net - LAG(mm_net) OVER (ORDER BY cot_date) AS d_mm, stocks_kbbl - LAG(stocks_kbbl) OVER (ORDER BY cot_date) AS d_stk FROM matched), shifted AS (SELECT cot_date, d_mm, d_stk, LEAD(d_stk,10) OVER (ORDER BY cot_date) AS lead10, LEAD(d_stk,8) OVER (ORDER BY cot_date) AS lead8, LEAD(d_stk,5) OVER (ORDER BY cot_date) AS lead5, LEAD(d_stk,3) OVER (ORDER BY cot_date) AS lead3, LEAD(d_stk,1) OVER (ORDER BY cot_date) AS lead1, LAG(d_stk,1) OVER (ORDER BY cot_date) AS lag1, LAG(d_stk,3) OVER (ORDER BY cot_date) AS lag3, LAG(d_stk,5) OVER (ORDER BY cot_date) AS lag5, LAG(d_stk,8) OVER (ORDER BY cot_date) AS lag8, LAG(d_stk,10) OVER (ORDER BY cot_date) AS lag10 FROM deltas) SELECT corr(d_mm, lead10) AS c_m10, corr(d_mm, lead8) AS c_m8, corr(d_mm, lead5) AS c_m5, corr(d_mm, lead3) AS c_m3, corr(d_mm, lead1) AS c_m1, corr(d_mm, d_stk) AS c_0, corr(d_mm, lag1) AS c_p1, corr(d_mm, lag3) AS c_p3, corr(d_mm, lag5) AS c_p5, corr(d_mm, lag8) AS c_p8, corr(d_mm, lag10) AS c_p10, count(*) AS n FROM shifted")
  2. Exploratory regime-split check (high vs. low position group x pre/post mid-2014); filter d.d_stk IS NOT NULL excludes the single earliest matched week (2010-01-05), which has no prior week to compute a stock change against
    Show tool call
    diff_in_diff(outcome="d_stk", treatment="high_position_group", post="post_price_crash")
  3. CFTC staff study: The Role of Speculators in the Crude Oil Futures Market
  4. Trader positions and the price of oil in the futures market (ScienceDirect)
  5. The Lead-Lag Relationship between Oil Futures and Spot Prices - A Literature Review (MDPI)
  6. Does Speculation Drive Oil Prices? (Resources for the Future)
  7. Speculation in oil and gas prices in times of geopolitical risks (ECB Economic Bulletin)
  8. The State of 'Speculative positions' in Oil Derivatives (Oxford Institute for Energy Studies)