Skip to content

Guides

How do I backtest NBA closing lines in Python?

A day of games with every sportsbook's opening and closing moneyline, spread and total, next to whether the home side covered and the total went over.

Code verified October 10, 2026. One call to get_odds, 5 credits.

The call

date is a US Eastern day. Loop it over a range to build a season.

import requests
import pandas as pd

API_KEY = "YOUR_API_KEY"

resp = requests.post(
    "https://mcp.dunkapi.com/v1/get_odds",
    headers={"x-api-key": API_KEY},
    json={
        "date": "2026-01-15",
    },
    timeout=60,
)
resp.raise_for_status()
data = resp.json()["data"]

rows = []
for g in data["games"]:
    for b in g["books"]:
        margin = g["home"]["score"] - g["away"]["score"]
        rows.append({
            "game": g["away"]["abbrev"] + " @ " + g["home"]["abbrev"],
            "book": b["provider"],
            "spread_close": b["home_spread_close"],
            "margin": margin,
            "home_covered": g["result"]["home_covered"],
            "total_close": b["total_close"],
            "points": g["home"]["score"] + g["away"]["score"],
            "over": g["result"]["over"],
        })
df = pd.DataFrame(rows)
df["cover_margin"] = df["margin"] + df["spread_close"]
print(df.to_string(index=False))
print(df.groupby("book")[["home_covered", "over"]].mean().round(2))

Replace YOUR_API_KEY with a key from your dashboard. Python needs requests and pandas (pip install requests pandas). JavaScript runs on Node 18 or newer, or in the browser.

What comes back

The data object of the response, trimmed to the first rows. Every field is real output from the run above.

{
  "games": [
    {
      "game_id": "401810433",
      "date": "2026-01-15",
      "status": "final",
      "home": {
        "team_id": "19",
        "abbrev": "ORL",
        "name": "Orlando Magic",
        "score": 118
      },
      "away": {
        "team_id": "29",
        "abbrev": "MEM",
        "name": "Memphis Grizzlies",
        "score": 111
      },
      "books": [
        {
          "provider": "DraftKings",
          "home_ml_open": -180,
          "home_ml_close": -230,
          "away_ml_open": 150,
          "away_ml_close": 190,
          "home_close_prob": 0.697,
          "away_close_prob": 0.345,
          "home_spread_open": -4.5,
          "home_spread_close": -5.5,
          "spread_home_odds": -108,
          "spread_away_odds": -112,
          "total_open": 230.5,
          "total_close": 227.5,
          "over_odds": -108,
          "under_odds": -112,
          "captured_at": "2026-10-07T01:15:36.497Z"
        }
      ],
      "result": {
        "median_home_spread": -5.5,
        "median_total": 227.5,
        "home_covered": true,
        "over": true
      }
    },
    "...8 more"
  ],
  "count": 9
}

What to do next

  • get_line_movement (10 credits). Daily snapshots of each line before tip-off, to test a rule on the open instead of the close.
  • get_ratings (2 credits). Our team Elo ratings, to compare a model's win chance with the implied probability.

More guides

Start with 500 free credits

No card. REST or MCP.