Metadata-Version: 2.4
Name: ercot
Version: 0.1.1
Summary: ERCOT market data in one line of Python — DAM/RTM prices, LMPs, load, generation, and more.
Author: Jeff
License-Expression: MIT
Project-URL: Homepage, https://github.com/yourusername/ercot
Project-URL: Repository, https://github.com/yourusername/ercot
Keywords: ercot,energy,electricity,settlement point prices,day-ahead market,real-time market,texas,power,lmp,spp,load zone,trading hub,deregulated
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Office/Business :: Financial
Classifier: Operating System :: OS Independent
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pandas>=2.0
Requires-Dist: requests>=2.28
Requires-Dist: numpy>=1.24
Requires-Dist: openpyxl>=3.1
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: build>=1.0; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# ercot

**ERCOT market data in one line of Python.**

A thin SDK for the ERCOT Public API that gives you Day-Ahead and Real-Time settlement point prices, LMPs, load data, generation mix, and more — all as pandas DataFrames with proper Central Prevailing Time DST handling and one-call Excel/CSV export.

```python
import ercot as er

er.configure("you@email.com", "your-password", "your-subscription-key")

df = er.dam("LZ_HOUSTON", "2025-07-01", "2025-07-31")
er.save_xlsx(df, "houston_dam_july.xlsx")
```

## Installation

```bash
pip install ercot
```

---

## Getting Your ERCOT API Credentials

You need three things: a **username** (email), a **password**, and a **subscription key**. All free. Here's exactly how to get them.

### Step 1: Register an Account

1. Go to **https://apiexplorer.ercot.com/**
2. Click **Sign Up** in the top-right corner
3. Fill in your **email address** and create a **password**
4. Complete the email verification (check your inbox for the confirmation link)
5. Once verified, sign in to the API Explorer

<!-- SCREENSHOT: apiexplorer.ercot.com sign-up page -->

### Step 2: Subscribe to the Public API

1. After signing in, click **Products** in the top navigation
2. Click **Public API** to open the product page
3. Enter a name for your subscription (e.g., "my-ercot-app") in the **Subscription name** field
4. Click **Subscribe**
5. Wait for your subscription to be approved (usually instant, but can take up to a few hours)

<!-- SCREENSHOT: Products page showing the Subscribe button -->

### Step 3: Get Your Subscription Key

1. After approval, click your **username** in the top-right corner → **Profile**
2. You'll see your subscription(s) listed
3. Click **Show** next to the **Primary key** field
4. Copy this key — this is your **subscription key** (also called `Ocp-Apim-Subscription-Key`)

<!-- SCREENSHOT: Profile page showing the subscription key (redacted) -->

### Step 4: Configure ercot

```python
import ercot as er

# This saves your credentials to ~/.ercot/credentials.json
# so you only need to do it once.
er.configure(
    "you@email.com",          # the email you registered with
    "your-password",           # the password you created
    "your-subscription-key",   # the Primary key from Step 3
)
```

That's it. All subsequent calls will use the saved credentials automatically.

### Alternative: Environment Variables

If you prefer not to save credentials to disk (e.g., in CI/CD or Docker):

```bash
export ERCOT_API_USERNAME="you@email.com"
export ERCOT_API_PASSWORD="your-password"
export ERCOT_API_SUBSCRIPTION_KEY="your-subscription-key"
```

### Alternative: Pass Directly

```python
c = er.client(
    username="you@email.com",
    password="your-password",
    subscription_key="your-key",
)
df = c.dam_spp("2025-07-01")
```

---

## Quick Start

```python
import ercot as er

# Day-Ahead prices — wide format (date + HE1..HE24 columns)
df = er.dam("LZ_HOUSTON", "2025-07-01", "2025-07-31")

# Real-Time prices — long format (one row per 15-min interval)
df = er.rtm("HB_NORTH", "2025-07-01", "2025-07-07", format="long")

# All load zones at once
df = er.dam("zones", "2025-01-01", "2025-01-31")

# Save to Excel (styled headers, alternating rows)
er.save_xlsx(df, "prices.xlsx")

# Save to CSV
er.save_csv(df, "prices.csv")
```

---

## Output Formats

### Wide Format (default)

One row per date per settlement point. Interval values are columns.

**DAM (24 hourly intervals):**

| date | settlement_point | HE1 | HE2 | HE3 | ... | HE24 |
|------|-----------------|------|------|------|-----|------|
| 2025-07-01 | LZ_HOUSTON | 28.54 | 25.12 | 23.87 | ... | 38.91 |
| 2025-07-01 | LZ_NORTH | 27.10 | 24.03 | 22.95 | ... | 37.44 |

**RTM (96 fifteen-minute intervals):**

| date | settlement_point | IE1 | IE2 | ... | IE96 |
|------|-----------------|------|------|-----|------|
| 2025-07-01 | LZ_HOUSTON | 24.12 | 25.67 | ... | 35.44 |

### Long Format

One row per interval with a CPT-aware `interval_ending` datetime.

**DAM:**

| date | settlement_point | hour | interval_ending | price |
|------|-----------------|------|-------------------------------|-------|
| 2025-07-01 | LZ_HOUSTON | 1 | 2025-07-01 01:00:00-05:00 | 28.54 |
| 2025-07-01 | LZ_HOUSTON | 2 | 2025-07-01 02:00:00-05:00 | 25.12 |

**RTM:**

| date | settlement_point | interval | interval_ending | price |
|------|-----------------|----------|-------------------------------|-------|
| 2025-07-01 | LZ_HOUSTON | 1 | 2025-07-01 00:15:00-05:00 | 24.12 |
| 2025-07-01 | LZ_HOUSTON | 2 | 2025-07-01 00:30:00-05:00 | 25.67 |

---

## DST Handling

ERCOT operates in Central Prevailing Time (CPT). ercot handles DST transitions correctly:

- **Spring forward** (March): Hour Ending 03:00 is skipped. DAM has 23 intervals; RTM has 92.
- **Fall back** (November): Hour Ending 02:00 repeats. ERCOT flags the second occurrence. DAM has 25 intervals; RTM has 100.

In long format, the `interval_ending` column is a timezone-aware timestamp (`America/Chicago`) with the correct UTC offset for each row — CDT intervals show `-05:00`, CST intervals show `-06:00`.

---

## Settlement Point Shortcuts

| Shortcut | Expands To |
|----------|-----------|
| `'LZ_HOUSTON'` | Single load zone |
| `['LZ_HOUSTON', 'HB_NORTH']` | Specific list |
| `'zones'` | All 8 load zones: LZ_HOUSTON, LZ_NORTH, LZ_SOUTH, LZ_WEST, LZ_AEN, LZ_CPS, LZ_LCRA, LZ_RAYBN |
| `'hubs'` | All 6 trading hubs: HB_BUSAVG, HB_HOUSTON, HB_NORTH, HB_SOUTH, HB_WEST, HB_PAN |
| `'all'` | All zones + hubs combined |

---

## Full API Reference

### Prices

| Function | Description |
|----------|-------------|
| `er.dam(points, start, end, format=)` | DAM Settlement Point Prices (hourly) |
| `er.rtm(points, start, end, format=)` | RTM Settlement Point Prices (15-min) |
| `er.dam_lmp(start, end, points)` | DAM Hourly LMPs |
| `er.rtm_lmp(start, end, points)` | RTM LMPs by Settlement Point (5-min) |

### Load

| Function | Description |
|----------|-------------|
| `er.system_load(start, end)` | Actual system load by weather zone |
| `er.load_forecast(start, end)` | 7-day load forecast by model/weather zone |

### Generation

| Function | Description |
|----------|-------------|
| `er.wind_production(start, end)` | Wind power — actual and forecast |
| `er.solar_production(start, end)` | Solar power — actual and forecast |
| `er.fuel_mix(start, end)` | Generation by fuel type |

### Grid / Constraints

| Function | Description |
|----------|-------------|
| `er.dam_shadow_prices(start, end)` | DAM shadow prices and binding constraints |
| `er.sced_shadow_prices(start, end)` | SCED shadow prices and binding constraints |

### Reference

| Function | Description |
|----------|-------------|
| `er.settlement_points_list()` | All settlement points and bus mapping |
| `er.list_endpoints()` | List all available endpoints |

### Export

| Function | Description |
|----------|-------------|
| `er.save_xlsx(df, "file.xlsx")` | Formatted Excel (styled headers, alternating rows) |
| `er.save_csv(df, "file.csv")` | Standard CSV export |

### Advanced

```python
# Get the full client for custom queries
c = er.client()
df = c.request("fuel_mix", {"deliveryDateFrom": "2025-07-01"})

# Hit an endpoint not in the registry
df = c.raw("/np6-xxx-cd/some_endpoint", {"param": "value"})
```

---

## Excel Export Features

`er.save_xlsx()` produces publication-quality spreadsheets:

- **Header row**: Dark teal background, white bold text, centered, with borders
- **Data rows**: Alternating white / light gray for readability
- **Price columns**: Automatically detected (HE*, IE*, *price*) and formatted as `$#,##0.00`
- **Column widths**: Auto-fitted to content
- **Frozen panes**: Header row stays visible when scrolling
- **Auto-filter**: Filter dropdowns on every column
- **Optional high-price highlighting**: Red background for prices above a threshold

```python
# Basic
er.save_xlsx(df, "prices.xlsx")

# Multi-sheet workbook
er.save_xlsx({
    "DAM Houston": dam_df,
    "RTM Houston": rtm_df,
    "Load": load_df,
}, "ercot_report.xlsx")

# Highlight extreme prices
er.save_xlsx(df, "prices.xlsx", highlight_high_prices=True, high_price_threshold=200)
```

---

## Architecture

```
ercot/
├── __init__.py      # Public API — er.dam(), er.rtm(), er.save_xlsx(), etc.
├── models.py        # Constants, enums, endpoint registry, timezone
├── auth.py          # OAuth2 ROPC token management, credential storage
├── client.py        # ErcotClient — one method per endpoint, pagination
├── formatting.py    # Wide/long conversion, DST-aware datetimes
└── export.py        # CSV and formatted XLSX output
```

Data flow: **ERCOT API → ErcotClient → DataFrame → (optional) formatting → (optional) export**

No local database, no caching, no stale data. Every call hits the live API.

---

## License

MIT
