Price Forecast Service
Welcome to this walkthrough on the Tyba Forecast API.
By the end of this guide, you’ll know how to retrieve the most recent energy and ancillary price forecasts, as well as how to retrieve older vintages of forecasts.
Setting Up:
First, ensure you’ve installed the Tyba Client. After
instantiating a Tyba client instance, one can access the forecast client by calling
client.forecast
from datetime import datetime, time
import pytz
import os
import pandas as pd
from tyba_client.client import Client
PAT = os.environ["TYBA_PAT"]
client = Client(PAT)
forecast = client.forecast
REST API vs. Python Client
Everything in this guide is shown using the Python client, but the same functionality is available directly over a plain REST API — useful if you’re calling the Tyba Forecast API from a language other than Python, or would simply rather not use the client.
Base URL: https://dev.tybaenergy.com/public/0.1/forecasts
Requests are authenticated with the same personal access token used above, passed as the Authorization
header (no Bearer prefix):
GET https://dev.tybaenergy.com/public/0.1/forecasts/most_recent_forecast
?object_name=<object_name>
&product=<product>
&start_time=<start_time>
&end_time=<end_time>
Authorization: <personal_access_token>
Each Python client method below maps to a corresponding REST endpoint under that base URL. The query
parameters for each endpoint match the corresponding Python method’s arguments (e.g. object_name,
product, start_time, end_time, quantiles, forecast_type, days_ago, before_time,
predictions_per_hour, prediction_lead_time_mins, horizon_mins).
Usage |
Python client method |
REST API endpoint |
|---|---|---|
Fetch most recent point forecasts |
most_recent_by_operating_datemost_recent (deprecated) |
|
Fetch vintaged point forecasts |
vintaged_by_operating_datevintaged (deprecated) |
|
Fetch point forecasts by vintage |
|
|
Fetch most recent probabilistic forecasts |
|
|
Fetch vintaged probabilistic forecasts |
|
|
Fetch probabilistic forecasts by vintage |
|
|
Fetch actual prices |
|
|
Fetching Energy Price Forecasts
For energy price forecasts (DA and RT), we recommend using the probabilistic endpoints which provide more detailed information about forecast uncertainty.
Probabilistic Forecast Endpoints
The Tyba Forecast API supports fetching probabilistic forecasts for energy prices. These endpoints allow users to request forecasts with specified quantiles, providing insights into the variability and confidence of the forecasted values.
For all probabilistic endpoints, the minimum quantile one can request is 0.05 and the maximum is 0.95.
Quantiles requested outside of the range will result in a 400 status code.
Fetching Most Recent Probabilistic Forecasts
Let’s suppose we’re interested in fetching probabilistic day-ahead and real-time energy price forecasts for a node from 10/10/23 until 10/17/23.
node_name = "HB_HOUSTON"
tz = pytz.timezone("US/Central") # Can be any timezone but results will always be returned in the node-local timezone
start_time = tz.localize(datetime(2023, 10, 10)) # localized datetime is required to avoid ambiguity
end_time = tz.localize(datetime(2023, 10, 17)) # localized datetime is required to avoid ambiguity
Day-Ahead Forecasts
For day-ahead forecasts, use the most_recent_probabilistic method without additional parameters:
# Fetching probabilistic day-ahead forecasts
quantiles = [0.10, 0.50, 0.90]
da_forecasts = forecast.most_recent_probabilistic("HB_HOUSTON", "da", start_time, end_time, quantiles)
Real-Time Forecasts
For real-time forecasts, especially if you need subhourly data, use the most_recent_probabilistic method with additional parameters:
Use product="rt_lmp" to fetch the RT energy price.
# Fetching probabilistic real-time forecasts with subhourly data
rt_forecasts = forecast.most_recent_probabilistic(
object_name="HB_HOUSTON",
product="rt_lmp",
start_time=start_time,
end_time=end_time,
quantiles=[0.10, 0.50, 0.90],
forecast_type="rolling-24hr",
predictions_per_hour=4, # Default for subhourly RT forecasts
prediction_lead_time_mins=0, # Default for rt_lmp
horizon_mins=1515 # Default for subhourly RT forecasts
)
Note: The parameters predictions_per_hour, prediction_lead_time_mins, and horizon_mins depend on the model you have access to. The values shown above are the recommended defaults for real-time forecasts.
Understanding the Returned Data
The most_recent_probabilistic endpoint returns a list of forecast dictionaries with the following structure:
{
'quantiles': [0.1, 0.5, 0.9],
'values': [14.60, 16.42, 18.93],
'mean_value': 16.55,
'datetime': '2023-10-10T00:00:00-05:00',
'forecasted_at': '2023-10-09T09:00:00-05:00',
'forecast_type': 'PROBABILISTIC'
}
Each element represents:
quantiles: The requested probability levelsvalues: The corresponding forecasted values for each quantilemean_value: The mean expected value for the pricedatetime: The date and time the forecast corresponds to (timezone-aware)forecasted_at: When the forecast was made (timezone-aware)forecast_type: The type of forecast returned —"PROBABILISTIC"for these endpoints
Note
forecast_type means two different things depending on whether you are reading it or writing it. As a
request parameter it selects the market — "day-ahead" or "rolling-24hr". In the response it
describes the kind of model that produced the forecast — "POINT" or "PROBABILISTIC". A day-ahead
probabilistic request therefore comes back with 'forecast_type': 'PROBABILISTIC', not 'day-ahead'.
Processing Probabilistic Forecasts
Here’s an example of how to process probabilistic forecasts into a more usable format:
def process(df):
return (
df.assign(datetime=lambda d: pd.to_datetime(d["datetime"]))
.explode(["quantiles", "values"])
.drop(["forecasted_at", "forecast_type"], axis=1)
.pivot(index="datetime", columns="quantiles", values="values")
.assign(mean=lambda d: d.apply(lambda x: df.loc[df["datetime"] == x.name, "mean_value"].iloc[0], axis=1))
.tz_localize(None)
)
This would convert the data into a pivot table like:
+----------------------+-----------+-----------+-----------+-----------+
| quantiles | 0.1 | 0.5 | 0.9 | mean |
+----------------------+-----------+-----------+-----------+-----------+
| datetime | | | | |
+======================+===========+===========+===========+===========+
| 2023-10-10 00:00:00 | 14.603392 | 16.423639 | 18.932876 | 16.55000 |
+----------------------+-----------+-----------+-----------+-----------+
| 2023-10-10 01:00:00 | 14.126083 | 15.910089 | 17.825208 | 15.97123 |
+----------------------+-----------+-----------+-----------+-----------+
Working with Vintaged Forecasts
Similar to point forecasts, you can also retrieve vintaged probabilistic forecasts.
Fetching Vintaged Probabilistic Forecasts
To fetch day-ahead probabilistic forecasts made at a specific time before the forecast period:
# Fetching vintaged probabilistic day-ahead forecasts made a day prior, before 10am
da_vintaged_forecasts = forecast.vintaged_probabilistic(
"HB_HOUSTON",
"da",
start_time,
end_time,
quantiles=[0.10, 0.50, 0.90],
days_ago=1,
before_time=time(10, 0)
)
For real-time probabilistic forecasts with subhourly data:
# Fetching vintaged probabilistic real-time forecasts with subhourly data
rt_vintaged_forecasts = forecast.vintaged_probabilistic(
object_name="HB_HOUSTON",
product="rt_lmp",
start_time=start_time,
end_time=end_time,
quantiles=[0.10, 0.50, 0.90],
days_ago=1,
before_time=time(10, 0),
forecast_type="rolling-24hr",
predictions_per_hour=4,
prediction_lead_time_mins=0,
horizon_mins=1515
)
Fetching Forecasts by Vintage
You may also fetch probabilistic forecasts based on when they were created:
# Fetching day-ahead probabilistic forecasts by vintage time
vintage_start_time = tz.localize(datetime(2023, 10, 9))
vintage_end_time = tz.localize(datetime(2023, 10, 10))
da_by_vintage = forecast.by_vintage_probabilistic(
"HB_HOUSTON",
"da",
[0.10, 0.50, 0.90],
vintage_start_time,
vintage_end_time
)
# Fetching real-time probabilistic forecasts by vintage time with subhourly data
rt_by_vintage = forecast.by_vintage_probabilistic(
object_name="HB_HOUSTON",
product="rt_lmp",
quantiles=[0.10, 0.50, 0.90],
vintage_start_time=vintage_start_time,
vintage_end_time=vintage_end_time,
forecast_type="rolling-24hr",
predictions_per_hour=4,
prediction_lead_time_mins=0,
horizon_mins=1515
)
Working with Day-Ahead / Real-Time Win Probabilities
During day-ahead bidding it may be useful in some cases (virtual bidding) to have a probability that the day-ahead price will exceed the real-time price for a given hour.
da_energy_gt_rt_energy_probability— the probability that the day-ahead price exceeds the real-time price for a delivery interval, P(DA > RT).rt_energy_gt_da_energy_probability— the complement, P(RT > DA).
The two are computed from the same underlying spread and always sum to 1.
Fetching Day-Ahead / Real-Time Win Probabilities
The probability of the spread direction is a single value and should be fetched with the point
forecast methods most_recent_by_operating_date, vintaged_by_operating_date and
by_vintage, passing the product names listed above:
from datetime import date, datetime, time, timedelta
import pytz
from tyba_client.forecast import Product
operating_date = date(2026, 9, 1)
# The most recent P(DA > RT) forecast for each interval of the operating date
p_da_gt_rt = forecast.most_recent_by_operating_date(
object_name="HB_HOUSTON",
product=Product.DA_ENERGY_GT_RT_ENERGY_PROBABILITY,
operating_date=operating_date,
)
# The P(RT > DA) forecast as it stood a day prior, before 10am.
# as_of is a naive datetime in the object's local timezone, written out in full:
p_rt_gt_da_vintaged = forecast.vintaged_by_operating_date(
object_name="HB_HOUSTON",
product=Product.RT_ENERGY_GT_DA_ENERGY_PROBABILITY,
operating_date=operating_date,
as_of=datetime.fromisoformat("2026-08-31T10:00"),
)
# The same request, deriving as_of from the operating date instead. Equivalent to the
# call above, and easier to reuse when looping over a range of operating dates.
p_rt_gt_da_vintaged = forecast.vintaged_by_operating_date(
object_name="HB_HOUSTON",
product=Product.RT_ENERGY_GT_DA_ENERGY_PROBABILITY,
operating_date=operating_date,
as_of=datetime.combine(operating_date - timedelta(days=1), time(10, 0)),
)
# Every P(DA > RT) forecast created within a vintage window.
# by_vintage is unchanged and still takes localized datetimes.
tz = pytz.timezone("US/Central")
vintage_start_time = tz.localize(datetime(2026, 9, 1))
vintage_end_time = tz.localize(datetime(2026, 9, 2))
p_da_gt_rt_by_vintage = forecast.by_vintage(
object_name="HB_HOUSTON",
product=Product.DA_ENERGY_GT_RT_ENERGY_PROBABILITY,
vintage_start_time=vintage_start_time,
vintage_end_time=vintage_end_time,
)
most_recent_by_operating_date and vintaged_by_operating_date take a plain date as the
operating_date and derive the query window from it, covering that whole local day — 24 hours
normally, 23 or 25 across a DST transition. vintaged_by_operating_date additionally takes an
as_of timestamp, the latest moment a forecast could have been made and still be returned; it is
a naive datetime, read in the object’s local timezone.
as_of may fall on the operating date itself, for a same-day vintage, and up to four days
before it. The server stops looking further back than that, so an earlier as_of would match
nothing; the client raises rather than returning an empty list.
Leave forecast_type, predictions_per_hour, prediction_lead_time_mins and
horizon_mins unset on by_vintage; the two by_operating_date methods do not accept them.
The probabilistic methods (most_recent_probabilistic, vintaged_probabilistic,
by_vintage_probabilistic) return a 400 for these products.
Working with Ancillary Prices
Ancillary prices are system-wide, not nodal. This means that to fetch them you pass the ISO name — e.g.
"ERCOT" — as the object name rather than a node name.
Ancillary prices should be fetched using the probabilistic endpoints —
most_recent_probabilistic, vintaged_probabilistic and by_vintage_probabilistic — for both
day-ahead and real-time products. This is the same guidance as for energy prices.
There are two families of ancillary products:
Service |
Day-ahead |
Real-time |
Resolution |
|---|---|---|---|
Regulation up |
|
|
DA: hourly, RT: 15-minute |
Regulation down |
|
|
DA: hourly, RT: 15-minute |
Responsive reserves |
|
|
DA: hourly, RT: 15-minute |
Non-spinning reserves |
|
|
DA: hourly, RT: 15-minute |
ERCOT Contingency Reserve Service |
|
|
DA: hourly, RT: 15-minute |
The day-ahead products above are the ones cleared in the day-ahead ancillary auction. The rt_-prefixed
products are the real-time ancillary prices that exist post-RTC+B, and they are a separate set of forecasts —
passing forecast_type="rolling-24hr" with a day-ahead product name (e.g. product="reg_down") does
not return them.
Day-Ahead Ancillary Prices
Day-ahead ancillary forecasts are hourly. Here’s how to fetch forecasts for reg_up and reg_down:
reg_up_forecasts = forecast.most_recent_probabilistic(
object_name="ERCOT",
product="reg_up",
start_time=start_time,
end_time=end_time,
quantiles=[0.10, 0.50, 0.90],
forecast_type="day-ahead",
predictions_per_hour=1, # hourly
horizon_mins=1440,
)
reg_down_forecasts = forecast.most_recent_probabilistic(
object_name="ERCOT",
product="reg_down",
start_time=start_time,
end_time=end_time,
quantiles=[0.10, 0.50, 0.90],
forecast_type="day-ahead",
predictions_per_hour=1, # hourly
horizon_mins=1440,
)
Each forecast dictionary has the same probabilistic structure documented above:
{
"quantiles": list[float], # The quantiles you requested, in the order you requested them
"values": list[float], # The forecasted value at each of those quantiles
"mean_value": float, # The mean of the forecast distribution
"datetime": string (ISO 8601), # The date and time the forecast is corresponding to (timezone-aware)
"forecasted_at": string (ISO 8601), # The date and time when the forecast was made (timezone-aware)
"forecast_type": string # "PROBABILISTIC"
}
For vintaged ancillary forecasts, use the corresponding probabilistic methods:
# Fetching vintaged ancillary forecasts a day prior, before 10am
reg_up_vintaged = forecast.vintaged_probabilistic(
object_name="ERCOT",
product="reg_up",
start_time=start_time,
end_time=end_time,
quantiles=[0.10, 0.50, 0.90],
forecast_type="day-ahead",
days_ago=1,
before_time=time(10, 0),
predictions_per_hour=1,
horizon_mins=1440,
)
# Fetching ancillary forecasts by vintage time
reg_down_by_vintage = forecast.by_vintage_probabilistic(
object_name="ERCOT",
product="reg_down",
quantiles=[0.10, 0.50, 0.90],
vintage_start_time=vintage_start_time,
vintage_end_time=vintage_end_time,
forecast_type="day-ahead",
predictions_per_hour=1,
horizon_mins=1440,
)
Real-Time Ancillary Prices
Real-time ancillary forecasts are 15-minute, so they take a different set of subhourly parameters:
rt_reg_down_forecasts = forecast.most_recent_probabilistic(
object_name="ERCOT",
product="rt_reg_down",
start_time=start_time,
end_time=end_time,
quantiles=[0.10, 0.50, 0.90],
forecast_type="rolling-24hr",
predictions_per_hour=4, # 15-minute intervals
prediction_lead_time_mins=0,
horizon_mins=1515,
)
The same parameters apply to the vintaged variants:
rt_ecrs_vintaged = forecast.vintaged_probabilistic(
object_name="ERCOT",
product="rt_ecrs",
start_time=start_time,
end_time=end_time,
quantiles=[0.10, 0.50, 0.90],
days_ago=1,
before_time=time(10, 0),
forecast_type="rolling-24hr",
predictions_per_hour=4,
prediction_lead_time_mins=0,
horizon_mins=1515,
)
The returned dictionaries have the same probabilistic structure documented above (quantiles, values,
mean_value, datetime, forecasted_at, forecast_type), at 15-minute resolution.
Note
An empty response almost always means the request did not match a live model. The most common causes are
using a day-ahead ancillary product name with forecast_type="rolling-24hr", or leaving
predictions_per_hour and horizon_mins at their defaults (1 and 1440) for a real-time
product.
With the above steps, you’ve successfully learned how to fetch various types of forecasts using the Tyba client. Feel free to reach out with any questions!
Additional Information on Subhourly Forecasts
Note: Subhourly forecasts are available for the real-time products — real-time energy ("rt_lmp")
and real-time ancillaries ("rt_reg_up", "rt_reg_down", "rt_reserves",
"rt_non_spin_reserves", "rt_ecrs") — and require specifying forecast_type = "rolling-24hr".
The parameters for subhourly forecasts are:
predictions_per_hour(int): The number of predictions made per hour (e.g., 4 for 15-minute intervals).prediction_lead_time_mins(int): The lead time for the predictions in minutes (e.g., 0 for predictions made at the forecast time).horizon_mins(int): The horizon in minutes for which the forecasts are made (typically 1515).
A request only returns data when these three values match a model you have access to, so they are effectively
required rather than optional for real-time products. The recommended values are predictions_per_hour=4,
prediction_lead_time_mins=0, and horizon_mins=1515 throughout, including for the real-time ancillary
products.
These parameters can be used in combination with existing methods for fetching both point and probabilistic forecasts, as shown in the examples above.