ManticDocs
Read forecasts

Build a forward curve from several forecasts

Line up numerical forecasts for successive dates into one curve, with its median and ranges at each date.

A forward curve shows one quantity, such as the Brent crude price, at a series of future dates. Each Mantic forecast covers a single date, so you build the curve from several forecasts.

Full script: forward_curve.py.

Find the forecasts with search or a tag filter, and take each one's date from its title or clarifications.

Fetch the forecasts together

Pass their IDs to GET /questions as a JSON array in question_ids:

curl --get "https://api.mantic.com/questions" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  --data-urlencode 'question_ids=[201,202,203]' \
  --data-urlencode "show_forecasts=true"

Each forecast's latest_forecast gives percentiles:

{
  "id": 202,
  "title": "What will the settlement price of Brent crude oil be on 30 November 2026?",
  "latest_forecast": {
    "id": 5120,
    "date": "2026-11-04T09:10:00Z",
    "type": "numerical",
    "percentiles": { "10": 84.12, "25": 92.59, "50": 101.73, "75": 111.44, "90": 123.29 },
    "thresholds": null,
    "intervals": null
  }
}

question_ids takes up to 100 values but a page holds 25 forecasts, so read every page with paginate(), keeping numerical runs:

questions = paginate(
    client,
    "/questions",
    "questions",
    {"question_ids": json.dumps(list(target_dates)), "show_forecasts": "true"},
)
return {
    question["id"]: question["latest_forecast"]
    for question in questions
    if question["latest_forecast"] is not None
    and question["latest_forecast"]["type"] == "numerical"
}

Order the points by date

Sort the forecasts by the date each one is for, and read the same percentiles from each run:

rows = []
for question_id, target in sorted(target_dates.items(), key=lambda item: item[1]):
    run = runs.get(question_id)
    if run is None:
        continue
    percentiles = run["percentiles"]
    rows.append(
        {"date": target, "run": run["date"][:10]}
        | {level: percentiles.get(level) for level in LEVELS}
    )
return rows

The 25th and 75th percentiles bound a 50% range, and the 10th and 90th an 80% range. Older runs can lack the 10th and 90th percentiles, so read them with .get().

Run it with a QUESTION_ID=DATE pair for each point:

python forward_curve.py 201=2026-11-16 202=2026-11-30 203=2026-12-15
| Date | 10th | 25th | Median | 75th | 90th | Run |
| --- | --- | --- | --- | --- | --- | --- |
| 2026-11-16 | 88.34 | 95.8 | 102.8 | 110.5 | 120.8 | 2026-11-04 |
| 2026-11-30 | 84.12 | 92.59 | 101.7 | 111.4 | 123.3 | 2026-11-04 |
| 2026-12-15 | 82.94 | 91.59 | 103.3 | 118.3 | 136.5 | 2026-10-26 |

Read the curve

Each point is a separate forecast, so neighbouring points can jump, and their runs can be from different days: show each run's date beside the curve. If you or your organisation have run a forecast with your own context, latest_forecast is that run for you; see Forecast with your own context.

To compare the curve with futures prices for the same dates, note that a futures price is closer to an expected value than to a median, so expect some gap where the forecast is skewed.

On this page