Quant Memo
Core

Structural Causal Models and Counterfactuals

A structural causal model writes each variable as a mechanical equation of its direct causes plus noise, which lets you ask not just what happened but what would have happened to a specific unit under a different action.

Prerequisites: Directed Acyclic Graphs for Causality, The Potential Outcomes Framework

A risk manager sees that a fund lost $2m last quarter, and asks: "would it have lost less if we'd cut the position size in half two months ago?" This isn't a question about correlations in historical data — it's a question about one specific fund, on one specific path of events, under a hypothetical action that never actually happened. Answering it needs more than a statistical model of average effects; it needs a mechanical description of how each variable in the system is actually generated, precise enough to "rewind" a single realized history and replay it differently. That mechanical description is a structural causal model, and the rewind-and-replay operation is a counterfactual.

An analogy: rerunning a specific chess game with one move changed

Imagine a recorded chess game and asking "what if White had played a different move on turn 12, everything else about both players' styles and the position held fixed?" You're not asking about White's average performance across many games (that's more like an average treatment effect) — you're asking about this exact game, holding fixed everything that happened before turn 12 and everything about the players that wasn't determined by the move itself, then mechanically working out how the rest would unfold under the new move. That requires a model detailed enough to simulate forward from a changed starting point while keeping every other "random seed" of the game identical to what actually happened.

The model, one symbol at a time

A structural causal model (SCM) specifies each variable ViV_i as a deterministic function of its direct causes Pai\text{Pa}_i (its "parents" in the DAG) and an independent noise term UiU_i:

Vi=fi(Pai,Ui)V_i = f_i(\text{Pa}_i, U_i)

In plain English: nothing is left unexplained by "randomness" alone — each variable is generated by a specific mechanism from its direct causes plus some unit-specific background noise that captures everything else about that individual (fund, trader, stock) not otherwise modeled. Given a full SCM, a counterfactual — "what would YY have been for this specific unit, had DD been set to dd' instead of what actually happened" — is computed in three steps: (1) abduction, infer the noise terms UiU_i consistent with what was actually observed for this unit; (2) action, replace the equation for DD with the fixed value dd' (a "do"-intervention, ignoring DD's normal causes); (3) prediction, propagate forward through the (modified) equations using the same inferred noise terms from step 1 to get the counterfactual YY.

Worked example 1: the risk manager's "what if," made concrete

Suppose the SCM is: Loss=0.02×PositionSize×MarketShock+U\text{Loss} = 0.02 \times \text{PositionSize} \times \text{MarketShock} + U, with MarketShock an observed market-wide factor and UU fund-specific noise. Actual data: PositionSize = $10m, MarketShock = −8%, observed Loss = $1.8m. Abduction: the modeled channel gives 0.02×10,000,000×0.08=16,0000.02 \times 10{,}000{,}000 \times 0.08 = 16{,}000, i.e. $16,000; the rest must be noise, so U=1,800,00016,000=1,784,000U = 1{,}800{,}000 - 16{,}000 = 1{,}784{,}000, i.e. $1,784,000 (most of this quarter's loss came from fund-specific noise, not the modeled channel — a simplification for illustration). Action: set PositionSize to $5m, keep MarketShock and UU fixed at their inferred values. Prediction: new Loss =0.02×5,000,000×0.08+1,784,000=1,792,000= 0.02 \times 5{,}000{,}000 \times 0.08 + 1{,}784{,}000 = 1{,}792{,}000, i.e. $1,792,000. The counterfactual loss barely changes — in this specific quarter, position size wasn't the dominant driver, the noise term was.

Worked example 2: why average effects and counterfactuals can disagree

Across many funds, suppose the average effect of halving position size is a genuine 40% reduction in average loss during shock quarters — a solid, well-estimated ATE (see ATE, ATT and other treatment effect estimands). But for this particular fund, the counterfactual loss reduction was only about 0.4%, because its realized loss was driven almost entirely by unit-specific noise rather than the position-size channel the average effect describes. Average effects and unit-level counterfactuals are genuinely different quantities from the same model, and a population-average recommendation can be nearly irrelevant to any one specific case.

−40% population ATE −0.4% this fund's counterfactual
The population-wide average effect and one specific unit's counterfactual can diverge sharply when that unit's own noise term, not the modeled channel, dominates its realized outcome.
1. Abduction infer U from observed data 2. Action fix D = d' by hand 3. Prediction propagate with same U Same unit, same noise term, different action — that's a counterfactual.
Computing a counterfactual for one specific unit is a three-step procedure: recover what made this unit unique, hypothetically change one input, and replay forward with everything else about the unit held fixed.

What this means in practice

Structural causal models formalize what "if only we'd done X differently" means precisely enough to compute, and they subsume the potential-outcomes notation from the potential outcomes framework as a special case — Yi(d)Y_i(d) is exactly the SCM's counterfactual prediction for unit ii under action D=dD=d. They matter most in post-mortems ("would this specific trade have lost money under a different stop-loss rule?"), individualized policy decisions, and mediation questions about which channel drove a specific outcome (see mediation analysis). The catch is that a full SCM requires committing to functional forms fif_i for every variable, which is a much stronger assumption than what's needed just to estimate an average effect.

A structural causal model writes each variable as a mechanism (a function of its direct causes plus unit-specific noise); a counterfactual uses that mechanism to infer what made one particular unit unique, then replays its history with one input changed and everything else about that unit held fixed.

It's tempting to treat a population-level average treatment effect as if it directly answers a unit-specific counterfactual question ("the average effect was +5%, so this specific fund would have gained 5%"). It generally doesn't: as the worked example shows, a specific unit's outcome can be dominated by its own idiosyncratic noise term, making its true counterfactual very different from the population average. Only a fully specified SCM, with noise terms inferred for that specific unit, can answer the individual-level question.

Related concepts

Practice in interviews

Further reading

  • Pearl, Causality, ch. 7
  • Pearl, Glymour & Jewell, Causal Inference in Statistics: A Primer, ch. 4
ShareTwitterLinkedIn