ERCOT logo

Constraint Shift Factors (Estimated)

Estimated sensitivity of every ERCOT pricing location's price to every binding transmission constraint: one row per constraint, contingency, and location, recomputed nightly over a rolling 60-day window.

Native record
run x constraint-contingency pair x settlement point
nightly (60-day rolling estimation window) publication cadence
Evidence
Reference data
Stable context used to interpret other datasets
Time basis
Point-in-time records
UTC · computed_at

Overview

This is what answers 'which nodes drive this constraint' and 'which constraints is this node exposed to'. Covers ERCOT's transmission constraints with their contingency and both named terminals, over 1,123 settlement points. shift_factor is in the ISO's orientation — POSITIVE means an incremental MW injected at that node increases loading on the constraint and the node is paid below system lambda while it binds; NEGATIVE means generation there relieves the constraint and earns a premium. These are ESTIMATES recovered from the price identity LMP = energy - sum(shift_factor x shadow_price), not a published PTDF matrix. They are measured RELATIVE TO THE MARKET'S REFERENCE — the identity yields SF(node) - SF(reference), so a single absolute value should be quoted as 'relative to the system reference' rather than as the node's PTDF, while DIFFERENCES between nodes are exact because the reference term cancels. Three further differences from a published matrix: they describe a NODE (a generator at that node inherits its location's value, and two units at one node are indistinguishable), they reflect the topology actually in effect across the window including outages rather than a stated base case, and every one carries a std_error from a 200-draw block bootstrap that resamples whole days. Read std_error before quoting shift_factor: an estimate whose absolute value is not at least twice its standard error is not distinguishable from zero. estimation_status says why a row has no number — 'thin_support' (in the fit, so it is controlled for, but under 12 binding intervals or under 3 distinct binding days), 'no_support' (under 3 binding intervals; not a regressor), or 'collinear' (it bound in lockstep with another constraint, so the two cannot be separated). Where from_station and to_station are populated they identify the monitored element and are NOT the constrained direction — the sign of shift_factor is what says which way. Every read must pin computed_at to one run, and must resolve that run through the companion atg.<iso>_constraint_sensitivity_runs ledger rather than off max(computed_at) here — rows are written in blocks that all carry the new timestamp, so an unfinished or abandoned run would otherwise look like the newest. Mixing runs averages a node across different network topologies.

Query contract

Resolve the latest run through the completed-run ledger, never off the sensitivities table: computed_at = (SELECT max(computed_at) FROM atg.ercot_constraint_sensitivity_runs). A run writes its rows in blocks that all carry the new computed_at, so max(computed_at) on atg.ercot_constraint_sensitivities can name a run still being written, or one that died halfway. Then filter estimation_status = 'estimated' and require BOTH abs(shift_factor) >= 2 * std_error AND std_error <= 0.25 before quoting or ranking a value.

Analysis rules

  • Never aggregate across computed_at. Two runs are two estimates of a network that may have changed between them, and averaging them describes neither.
  • Rank nodes by abs(shift_factor), not by shift_factor: the sign says which side of the constraint a node is on, and the magnitude is the exposure. Report the sign alongside the rank, and describe such a ranking as exposure RELATIVE TO THE MARKET REFERENCE — see the caution; it is not a reference-free physical ordering.

Related analysis

  • which nodes a binding constraint moved and congestion attribution · Join on constraint_name and contingency_name. Congestion at node i from constraint c in an interval is shift_factor x shadow_price, negated: LMP = energy - sum(shift_factor x shadow price).
  • the congestion component this table was fitted to · location_id is the same settlement-point identifier. congestion_usd_mwh is the left-hand side of the regression these coefficients come from.

Interpretation

  • These are estimated NODE sensitivities recovered from prices, not a published PTDF matrix. A generator inherits its settlement point's value and two units at the same node are indistinguishable here.
  • shift_factor is measured RELATIVE TO THE MARKET'S REFERENCE, because that is what a congestion component is: the identity gives SF(node) - SF(reference), not an absolute shift factor against a named slack bus. Measured across ~1,118 ERCOT settlement points, the cross-sectional mean per constraint is within 0.005 of zero, which is a distributed reference. Say 'relative to the system reference' when quoting one absolute value; do not describe it as the node's PTDF.

Schema20

Column
Type
computed_atDateTime64(3, 'UTC')
run_idString
constraint_nameLowCardinality(String)
contingency_nameLowCardinality(String)
location_idLowCardinality(String)
shift_factorNullable(Float64)
std_errorNullable(Float64)
estimation_statusLowCardinality(String)
n_intervalsUInt32
n_binding_intervalsUInt32
n_binding_daysUInt16
n_resamplesUInt16
node_r2Nullable(Float64)
node_residual_rms_usd_mwhNullable(Float64)
window_startDateTime64(3, 'UTC')
window_endDateTime64(3, 'UTC')
from_stationLowCardinality(String)
to_stationLowCardinality(String)
shadow_feed_interval_coverageFloat32
method_versionLowCardinality(String)

Sample data

No preview available.

Coverage

No time-indexed data — coverage isn’t applicable to this dataset.