MISO Data Guide

Overview

The Midcontinent Independent System Operator (MISO) operates one of the world’s largest energy markets, serving 45 million people across 15 U.S. states and Manitoba, Canada. ISO-DART provides comprehensive access to MISO’s Data Exchange REST API for pricing, load, generation, and market operations data.

Quick Reference

Data Category

Update Frequency

Historical Availability

API Requirement

LMP (DA/RT)

5-min/Hourly

2009-present

Pricing API Key

MCP

5-min/Hourly

2009-present

Pricing API Key

Load/Demand

5-min/Hourly

2010-present

LGI API Key

Fuel Mix

5-min intervals

2014-present

LGI API Key

Generation

Hourly

2010-present

LGI API Key

MISO Markets

MISO operates an integrated day-ahead and real-time market system:

Day-Ahead Market

  • Purpose: Schedule generation and manage congestion for next operating day

  • Timeline: Closes at 11 AM CT, results posted by 4 PM CT

  • Resolution: Hourly intervals

  • Products: Energy, Operating Reserves, Ramp Capability

Real-Time Market

  • Purpose: Balance supply and demand in real-time

  • Timeline: Continuous 5-minute dispatch

  • Resolution: 5-minute intervals

  • Products: Energy, Regulation, Spinning/Supplemental Reserves

API Access & Authentication

MISO uses a REST API with two separate products requiring different API keys:

Pricing API

Covers:

  • LMP data (ExAnte/ExPost)

  • Market Clearing Prices (MCP)

  • Ancillary Services pricing

Get your key at: https://data-exchange.misoenergy.org/

Load, Generation & Interchange (LGI) API

Covers:

  • Load and demand data

  • Generation by fuel type

  • Fuel on the margin

  • Interchange flows

  • Outages and constraints

Get your key at: https://data-exchange.misoenergy.org/

Configuration Setup

Create user_config.ini in your project root:

[miso]
pricing_api_key = your-pricing-api-key-here
lgi_api_key = your-lgi-api-key-here
data_dir = data/MISO
max_retries = 3
timeout = 30

Or use the template generator:

from lib.iso.miso import MISOConfig

# Creates template config file
MISOConfig.create_template_ini()
# Edit user_config.ini with your keys

Available Data Types

1. Pricing Data

Locational Marginal Prices (LMP)

MISO LMP data is available in four variants:

  • Day-Ahead ExAnte: Forward-looking DA market prices

  • Day-Ahead ExPost: Final settled DA market prices

  • Real-Time ExAnte: 5-minute ahead RT prices

  • Real-Time ExPost: Final settled RT prices

Example - Download DA ExAnte LMP:

from datetime import date
from lib.iso.miso import MISOClient, MISOConfig

# Load configuration with API keys
config = MISOConfig.from_ini_file()
client = MISOClient(config)

# Download LMP data
data = client.get_lmp(
    lmp_type='da_exante',
    start_date=date(2024, 1, 1),
    duration=30,
    node='ALTW.WELLS1'  # Optional: filter by node
)

# Save to CSV
if data:
    client.save_to_csv(data, 'miso_da_exante_lmp.csv')

Output: data/MISO/miso_da_exante_lmp_2024-01-01.csv

Key Columns:

  • node: Pricing node identifier

  • value: LMP in $/MWh

  • lmp: Total LMP

  • mcc: Marginal Congestion Component

  • mlc: Marginal Loss Component

  • interval: Date/time of interval

Market Clearing Prices (MCP)

Ancillary service market clearing prices by zone and product.

# ASM Day-Ahead ExAnte MCP
data = client.get_mcp(
    mcp_type='asm_da_exante',
    start_date=date(2024, 1, 1),
    duration=30,
    zone='MISO',  # Optional: filter by zone
    product='RegUp'  # Optional: filter by product
)

MCP Types:

  • asm_da_exante: ASM Day-Ahead ExAnte

  • asm_da_expost: ASM Day-Ahead ExPost

  • asm_rt_exante: ASM Real-Time ExAnte

  • asm_rt_expost: ASM Real-Time ExPost

  • asm_rt_summary: ASM Real-Time Summary

Ancillary Service Products:

  • RegUp: Regulation Up

  • RegDown: Regulation Down

  • Spin: Spinning Reserve

  • Supp: Supplemental Reserve

2. Load & Demand Data

System Demand

Multiple demand data types available:

# Real-Time Actual Load
data = client.get_demand(
    demand_type='rt_actual',
    start_date=date(2024, 1, 1),
    duration=30,
    region='MISO',  # Optional: filter by region
    time_resolution='daily'  # or 'hourly', '5min'
)

Demand Types:

  • da_demand: Day-Ahead scheduled demand

  • rt_forecast: Real-Time demand forecast

  • rt_actual: Real-Time actual load

  • rt_state_estimator: State estimator load

Load Forecasts

Medium-term load forecasts:

data = client.get_load_forecast(
    start_date=date(2024, 1, 1),
    duration=30,
    region='MISO',
    time_resolution='daily'
)

Use Cases:

  • Planning and operations

  • Load shape analysis

  • Forecast accuracy studies

3. Generation Data

Fuel Mix

Real-time fuel on the margin (5-minute resolution):

data = client.get_fuel_mix(
    start_date=date(2024, 1, 1),
    duration=30,
    region='MISO',
    fuel_type='Coal'  # Optional filter
)

Fuel Types: Coal, Gas, Nuclear, Wind, Solar, Hydro, Other

Generation by Type

Various generation data products:

# Real-Time fuel type generation
data = client.get_generation(
    gen_type='rt_fuel_type',
    start_date=date(2024, 1, 1),
    duration=30
)

Generation Types:

  • da_cleared_physical: DA cleared physical generation

  • da_cleared_virtual: DA cleared virtual generation

  • da_fuel_type: DA generation by fuel type

  • da_offered_ecomax: DA offered economic max

  • rt_cleared: RT cleared generation

  • rt_fuel_type: RT generation by fuel type

  • rt_fuel_margin: RT fuel on margin

4. Interchange Data

Net interchange flows between MISO and neighboring systems:

data = client.get_interchange(
    interchange_type='rt_net_actual',
    start_date=date(2024, 1, 1),
    duration=30,
    region='MISO',
    adjacent_ba='PJM'  # Optional filter
)

Interchange Types:

  • da_net_scheduled: DA scheduled interchange

  • rt_net_actual: RT actual interchange

  • rt_net_scheduled: RT scheduled interchange

  • historical: Historical net scheduled

5. Outages & Constraints

Binding Constraints

Real-time binding transmission constraints:

data = client.get_binding_constraints(
    start_date=date(2024, 1, 1),
    duration=30,
    interval='all'  # or specific interval
)

Use Cases:

  • Congestion analysis

  • Price driver identification

  • Transmission planning

Outage Data

Generation outage forecasts and actual outages:

# Outage forecast
data = client.get_outages(
    outage_type='forecast',
    start_date=date(2024, 1, 1),
    duration=30
)

# Real-time outages
data = client.get_outages(
    outage_type='rt_outage',
    start_date=date(2024, 1, 1),
    duration=30
)

Common Workflows

1. Daily Price Update

from datetime import date, timedelta
from lib.iso.miso import MISOClient, MISOConfig

def daily_miso_update():
    """Download yesterday's MISO data."""
    yesterday = date.today() - timedelta(days=1)

    config = MISOConfig.from_ini_file()
    client = MISOClient(config)

    # Download LMP
    lmp_data = client.get_lmp('da_exante', yesterday, 1)
    if lmp_data:
        client.save_to_csv(lmp_data, f'miso_lmp_{yesterday}.csv')
        print(f"✓ Downloaded LMP for {yesterday}")

    # Download fuel mix
    fuel_data = client.get_fuel_mix(yesterday, 1)
    if fuel_data:
        client.save_to_csv(fuel_data, f'miso_fuel_{yesterday}.csv')
        print(f"✓ Downloaded fuel mix for {yesterday}")

if __name__ == '__main__':
    daily_miso_update()

2. Fuel Mix Analysis

import pandas as pd
import matplotlib.pyplot as plt
from datetime import date
from lib.iso.miso import MISOClient, MISOConfig

# Download data
config = MISOConfig.from_ini_file()
client = MISOClient(config)

data = client.get_fuel_mix(
    start_date=date(2024, 1, 1),
    duration=31
)

client.save_to_csv(data, 'miso_fuel_jan2024.csv')

# Load and analyze
df = pd.read_csv('data/MISO/miso_fuel_jan2024.csv')

# Convert to datetime
df['datetime'] = pd.to_datetime(df['interval'])

# Group by fuel type and date
daily_by_fuel = df.groupby([
    df['datetime'].dt.date,
    'fuelType'
])['value'].sum().unstack()

# Plot stacked area chart
fig, ax = plt.subplots(figsize=(14, 8))
daily_by_fuel.plot.area(ax=ax, alpha=0.8)
ax.set_xlabel('Date')
ax.set_ylabel('Generation (MWh)')
ax.set_title('MISO Fuel Mix - January 2024')
ax.legend(title='Fuel Type', bbox_to_anchor=(1.05, 1))
plt.tight_layout()
plt.savefig('miso_fuel_mix_jan2024.png', dpi=300)

3. Load Correlation Study

import pandas as pd
import numpy as np
from scipy.stats import pearsonr

# Load LMP and demand data
lmp_df = pd.read_csv('data/MISO/miso_lmp_jan2024.csv')
load_df = pd.read_csv('data/MISO/miso_rt_actual_load_jan2024.csv')

# Merge on timestamp
lmp_df['datetime'] = pd.to_datetime(lmp_df['interval'])
load_df['datetime'] = pd.to_datetime(load_df['interval'])

# Get hourly averages
lmp_hourly = lmp_df.groupby(
    lmp_df['datetime'].dt.floor('H')
)['lmp'].mean()

load_hourly = load_df.groupby(
    load_df['datetime'].dt.floor('H')
)['value'].mean()

# Calculate correlation
correlation, p_value = pearsonr(
    lmp_hourly.values,
    load_hourly.values
)

print(f"Price-Load Correlation: {correlation:.3f}")
print(f"P-value: {p_value:.6f}")

# Scatter plot
plt.figure(figsize=(10, 8))
plt.scatter(load_hourly.values, lmp_hourly.values, alpha=0.5)
plt.xlabel('System Load (MW)')
plt.ylabel('LMP ($/MWh)')
plt.title(f'MISO Load vs. Price\nCorrelation: {correlation:.3f}')
plt.grid(True, alpha=0.3)

# Add trend line
z = np.polyfit(load_hourly.values, lmp_hourly.values, 1)
p = np.poly1d(z)
plt.plot(load_hourly.values, p(load_hourly.values), "r--", linewidth=2)

plt.tight_layout()
plt.savefig('miso_price_load_correlation.png', dpi=300)

Data Quality & Limitations

Update Schedule

  • DA ExAnte LMP: Available ~4 PM CT day before operating day

  • DA ExPost LMP: Available ~4 PM CT day after operating day

  • RT ExAnte LMP: Available within 5 minutes of interval

  • RT ExPost LMP: Available ~1 hour after interval

  • Fuel Mix: Updated every 5 minutes

Historical Data

Data Type

Start Date

Notes

LMP (All types)

January 2009

Complete history

MCP

January 2009

Complete history

Load/Demand

January 2010

Complete history

Fuel Mix

March 2014

When tracking began

Generation Data

January 2010

Varies by product

Known Issues

  1. API Rate Limits: 100 requests/minute (adjustable in config)

  2. Large Date Ranges: Use pagination for requests over 90 days

  3. Node Names: May change over time; check MISO node list

  4. Time Zones: All times in Central Time (CT)

Rate Limiting

from lib.iso.miso import MISOConfig

# Adjust rate limit delay (seconds between requests)
config = MISOConfig(
    rate_limit_delay=0.8  # Default is 0.6
)

client = MISOClient(config)

Performance Tips

1. Use Filters

Filter data at the API level to reduce response size:

# Better: filter by node
data = client.get_lmp(
    lmp_type='da_exante',
    start_date=date(2024, 1, 1),
    duration=30,
    node='SPECIFIC.NODE'  # Only this node
)

# Avoid: downloading all nodes then filtering
data = client.get_lmp('da_exante', date(2024, 1, 1), 30)
filtered = {k: v for k, v in data.items() if 'SPECIFIC.NODE' in str(v)}

2. Adjust Time Resolution

Use coarser resolution when possible:

# For daily analysis, use daily resolution
data = client.get_demand(
    demand_type='rt_actual',
    start_date=date(2024, 1, 1),
    duration=30,
    time_resolution='daily'  # Much faster than '5min'
)

3. Batch Processing

Process multiple months in a loop:

from datetime import date
from dateutil.relativedelta import relativedelta

start = date(2024, 1, 1)

for month in range(12):
    month_start = start + relativedelta(months=month)
    month_end = month_start + relativedelta(months=1)
    days = (month_end - month_start).days

    print(f"Processing {month_start.strftime('%B %Y')}")

    data = client.get_lmp(
        lmp_type='da_exante',
        start_date=month_start,
        duration=days
    )

    if data:
        filename = f"miso_lmp_{month_start.strftime('%Y%m')}.csv"
        client.save_to_csv(data, filename)

Troubleshooting

Issue: “Authentication failed - check API key”

Solutions:

  1. Verify API key is correct in user_config.ini

  2. Check you’re using the right key for the right product:

    • Pricing API key → LMP, MCP data

    • LGI API key → Load, generation, interchange data

  3. Ensure API subscription is active at https://data-exchange.misoenergy.org/

Issue: “No data returned”

Solutions:

Issue: Rate limit errors (429)

Solutions:

# Increase delay between requests
config = MISOConfig(
    rate_limit_delay=1.0  # Increase from default 0.6
)

# Or reduce requests per batch
# Download in smaller chunks

Issue: Timeout errors

Solutions:

# Increase timeout
config = MISOConfig(
    timeout=60  # Default is 30 seconds
)

# Or break into smaller date ranges

Additional Resources

Next Steps

  • api-keys - Detailed API key setup guide

  • pricing - MISO Pricing Data Deep Dive

  • load-gen - Load and Generation Analysis

  • ISO Coverage - Return to ISO Index