Market Analysis

Causal DAG, Driver Selection and Model Results

The causal structure behind the Petro4Cast demand models, the drivers each model uses, the scenario effects, and the accuracy of the models against the univariate benchmark.

Section 1Causal DAG

Production, inventory and trade nodes are folded into the refinery box here; the full graph is in Appendix A.

  • Solid arrows are causal paths.
  • The dashed arrow from Macro activity is the confounding path: macro activity drives both crude prices and demand, so every effect of price on demand is estimated with macro activity in the adjustment set.
  • Orange arrows are the primary treatment channel for the oil shock scenario.
  • The dotted arrow is the availability cap. Refinery output limits demand only in extreme weeks; in the project data this occurred in September 2005 and February 2021.
  • Confidence on each edge: HIGH when it is seen in several events and in the lag-correlation scan, MED when it is seen in some events or as a modest correlation, LOW when it rests on a domain assumption.

Section 2Events used as model flags

Flag columnsev_demandev_supplygeosprcold_snap

Note: The crude recovery of 2009 to 2011, the recovery of 2020 to 2022, the rate tightening of 2022 to 2023 and the PMI decoupling of 2022 to 2025 are macro regimes rather than dated shocks, and are not flagged.

Section 3Causal assumptions

1

Adjustment set.

Every price-on-demand effect conditions on macro activity, season, the demand-shock flags and lagged inventories, and never on same-week inventories, refinery output or CPI, which sit downstream of demand.

2

No unmeasured confounding.

Weather and consumer mobility are both observed. DoWhy's unobserved-confounder test ran for all nine estimands and every effect kept its sign.

3

Temporal precedence.

Prices enter at a lag of zero or more weeks and macro variables at one or more weeks, with no look-ahead on the weekly grid.

4

Stationarity and structural breaks.

Effects are estimated on log year-on-year changes, with breaks dated at September 2001, September 2008, March 2020, February 2021 and March 2026.

5

Positivity.

All four scenario shocks lie inside the observed range: a 25% WTI rise against 59% observed in 2026, a 10-point utilization fall observed 11 times, a 5% industrial production fall observed in 2015 to 2016.

6

Refutation.

Placebo-treatment, random-common-cause and data-subset refuters ran for all nine estimands, and every estimate held.

7

EIA accounting identity.

Product supplied is an accounting identity, not a consumption survey, so supply nodes reach the demand models only through prices and the event flags.

Section 4Drivers in the final models

The causal DAG produced the candidate drivers; however, this alone did not guarantee any of them a spot in the final model.

So we had to test even further:

  • Five windows: 2000–2026, 2005–2026, 2010–2026, 2015–2026, and 2021–2026
  • Each tested at two forecast horizons 12 and 24 weeks = 10 windows in total
  • Each window tuned separately: 40 Optuna trials each, 400 per fuel, 1,200 across all three fuels

To make the final model, a driver had to:

  • Keep the same sign and stay stable across all 10 windows
  • Forecast at least as well with its real future value as with a guessed one. If the guess won, the driver got cut

Gasoline

Drivers in the final model

  • Retail gasoline price: 8 weeks lag
  • Vehicle miles travelled: same week
  • Demand-shock flag
  • Supply-disruption flag

Removed, and why

  • Same-week retail price: moves with the 8-week lag, so it added nothing new
  • Employment: never mattered, and even flipped direction around 2020
  • Days of supply: made forecasts worse across the board, not just gasoline

Distillate

Drivers in the final model

  • Heating degree days: same week
  • Manufacturing PMI: 13 weeks lag
  • Demand-shock flag
  • Supply-disruption flag

Removed, and why

  • Industrial production index: pointed the wrong direction
  • Retail diesel price: known mode lost to held mode in all 10 windows, a clear sign it wasn't real
  • Dollar index: pointed the wrong direction and stayed significant in every window it entered
  • Days of supply: same as gasoline

Jet fuel

Drivers in the final model

  • Vehicle miles travelled: same week
  • Employment: 4 weeks lag
  • Demand-shock flag
  • Supply-disruption flag

Removed, and why

  • Jet spot price: pointed the wrong direction and stayed significant in nearly every window
  • Days of supply: same as gasoline

Section 5Scenario effects

Oil Shock and Slowdown apply the DoWhy backdoor-adjusted effect to the baseline forecast, phased in over the lag of the estimand, because the regression coefficients on price and macro activity are confounded. Refinery Down runs through the fitted model: the utilization shock moves the crack spread, the crack spread moves retail prices, and the supply-disruption flag is set for the shock weeks. Every effect passed the placebo and data-subset refuters and was identical across every valid adjustment set DoWhy found.

ScenarioShockGasolineDistillateJet fuel
Oil shockWTI rises 25%−0.36% demand (p = 0.003)+0.24% demand, not significant+0.32% demand, not significant
Refinery downUtilization falls 10 points for 2 weeks−3.4% output (p < 0.001)−9.3% output (p < 0.001)−14.5% output (p < 0.001)
SlowdownIndustrial production falls 5%+0.89% demand, not significant−3.18% demand (p = 0.012)−2.72% demand
CounterfactualRemoving a flagged event to see what would have happened instead are on the Counterfactual page →

Section 6Results against the benchmark

MASE is the forecast error divided by the error of a seasonal-naive forecast, so 1.0 equals seasonal naive and lower is better. Held mode extrapolates the future driver values and never sees them. The benchmark is the better of univariate SARIMAX and ETS in each of 10 training-window and horizon combinations per fuel.

Gasoline and jet fuel beat the benchmark, both on average and in 7 of the 10 windows, but distillate did not. Each of its drivers held up individually in Section 4, yet combined, they underperform: giving the model their true future values scores worse than giving it guessed values, in every window. That means distillate's forecast relies on its own seasonal pattern and recent history rather than on its drivers.

Even so, the benchmark produces only a single forecast number, while the causal model also breaks down what is driving each forecast, applies backdoor-adjusted effects with refutation checks, and runs all four scenario branches.

Appendix ADAG in DOT form

Variable names match the companion Python module, which the forecasting notebook imports for the DoWhy identification step.

digraph {
season; weather; cold_snap; ev_supply; ev_demand; geo; spr; rate;
macro; mobility; cpi;
wti; crack; p_gas; p_dsl; p_jet;
crude_prod; crude_stk; util; prod_gas; prod_dst; prod_jet; trade_gas; trade_dst; trade_jet; stk_gas; stk_dst; stk_jet;
d_gas; d_dst; d_jet;
cold_snap -> ev_supply; weather -> d_dst;
season -> util; season -> mobility; season -> d_gas; season -> d_dst; season -> d_jet;
ev_supply -> util; ev_supply -> crude_prod;
ev_demand -> macro; ev_demand -> mobility;
geo -> wti; spr -> wti; spr -> crude_stk;
rate -> macro;
macro -> d_dst; macro -> d_jet; macro -> d_gas; macro -> mobility;
mobility -> d_gas; mobility -> d_jet;
macro -> wti; crude_prod -> wti; crude_prod -> crude_stk;
wti -> p_gas; wti -> p_dsl; wti -> p_jet;
crack -> p_gas; crack -> p_dsl; crack -> p_jet;
util -> crack;
p_gas -> d_gas; p_dsl -> d_dst; p_jet -> d_jet;
p_gas -> cpi; p_dsl -> cpi;
crude_stk -> util;
util -> prod_gas; util -> prod_dst; util -> prod_jet;
prod_gas -> stk_gas; prod_dst -> stk_dst; prod_jet -> stk_jet;
trade_gas -> stk_gas; trade_dst -> stk_dst; trade_jet -> stk_jet;
crack -> trade_gas; crack -> trade_dst; crack -> trade_jet;
prod_gas -> d_gas; prod_dst -> d_dst; prod_jet -> d_jet;
}

Four feedback relationships enter at a one-week lag to keep the graph acyclic and are not drawn: product stocks to the crack spread, the crack spread to utilization, product stocks to trade, and gasoline and distillate demand to utilization.