On the wire · 13:54:57 UTC Live 72 · Prematch 8,062 · Markets 32,474 Vol. I · No. 1 · Monday 9 November 2026 wss://pinnfeed.io/v1/stream
PinnFeed
Guide · CLV

Track closing line value against Pinnacle's closing price

Updated · PinnFeed · 3 min read

Closing line value (CLV) compares the price you took with the market's final price before kickoff. If you consistently beat Pinnacle's closing fair price, you are very likely a winning bettor, long before your profit and loss can prove it. PinnFeed records the closing Pinnacle price of every market when an event starts and serves it from /v1/closing.

Step 1 — Know the formula#

CLV = price_taken / closing_fair_price − 1

Use the no-vig closing price, not the raw one: beating a price that still contains Pinnacle's margin is easier and overstates your edge. If you took 2.10 and the market closed at a fair 1.95, your CLV is +7.7%.

Step 2 — Log each bet#

Every bet needs the event id, the market key and the outcome. You get all three from the API: the event id, the market key (s;0;m is the full-game moneyline, s;0;ou;2.5 the full-game total 2.5) and the outcome name (home, away, draw, over, under).

Step 3 — Run the tracker#

Install requests, save the script as clv.py, and log bets as you place them:

# clv.py: log your bets and measure closing line value (CLV) against Pinnacle's no-vig close.
# pip install requests   Usage:
#   python clv.py bet <event_id> <market_key> <outcome> <price>   e.g. bet 1640141281 "s;0;m" home 2.10
#   python clv.py report
import os, sqlite3, sys, requests

BASE = os.environ.get("PINNFEED_BASE", "https://pinnfeed.io")
KEY = os.environ["PINNFEED_KEY"]
db = sqlite3.connect(os.environ.get("CLV_DB", "bets.db"))
db.execute("CREATE TABLE IF NOT EXISTS bets (event_id INTEGER, market_key TEXT, outcome TEXT, price REAL, "
           "close_fair REAL, placed_at TEXT DEFAULT CURRENT_TIMESTAMP)")


def closing_fair(event_id, market_key, outcome):
    r = requests.get(f"{BASE}/v1/closing/{event_id}", headers={"x-api-key": KEY}, timeout=20)
    if r.status_code == 404:
        return None                      # not closed yet (or never captured)
    r.raise_for_status()
    for rec in r.json()["markets"]:
        if rec["market"]["key"] == market_key:
            for o in rec["market"]["outcomes"]:
                if o["name"] == outcome:
                    return o["fair"]
    return None


if sys.argv[1:2] == ["bet"]:
    _, _, event_id, market_key, outcome, price = sys.argv
    db.execute("INSERT INTO bets (event_id, market_key, outcome, price) VALUES (?,?,?,?)",
               (int(event_id), market_key, outcome, float(price)))
    db.commit()
    print("logged")
else:
    rows = db.execute("SELECT rowid, event_id, market_key, outcome, price, close_fair FROM bets").fetchall()
    clvs = []
    for rowid, event_id, key, outcome, price, close in rows:
        if close is None:
            close = closing_fair(event_id, key, outcome)
            if close:
                db.execute("UPDATE bets SET close_fair=? WHERE rowid=?", (close, rowid))
        if close:
            clv = price / close - 1
            clvs.append(clv)
            print(f"{event_id} {key} {outcome}: took {price}, closed fair {close:.3f} -> CLV {clv:+.2%}")
        else:
            print(f"{event_id} {key} {outcome}: took {price}, waiting for close")
    db.commit()
    if clvs:
        print(f"average CLV over {len(clvs)} bets: {sum(clvs) / len(clvs):+.2%}")
python clv.py bet 1640229582 "s;0;m" home 2.10
python clv.py report
1640229582 s;0;m home: took 2.1, closed fair 3.181 -> CLV -33.99%
999 s;0;m home: took 1.9, waiting for close
average CLV over 1 bets: -33.99%

Run report from cron after kickoffs; each bet's closing price is fetched once and stored.

Step 4 — Read the result#

  • Over a few hundred bets, an average CLV of +2% or more is a strong sign of a real edge.
  • Negative CLV with positive profit usually means luck. Expect it to fade.
  • Split CLV by sport, market and how early you bet: it shows where your edge actually comes from.

Step 5 — Pull a whole day#

For research or backtests, fetch every closing line for a UTC day:

curl -H "x-api-key: $PINNFEED_KEY" "https://pinnfeed.io/v1/closing?date=2026-11-08&sport=soccer&main=true"

Questions people ask#

Which plan includes closing odds?#

Closing-line history is included from Builder (€99/month) and on Pro and Enterprise.

When is the closing price captured?#

When the event starts: the last prematch price of each market is stored with a capture timestamp.

How far back does the archive go?#

It starts on the day the service launched and grows every day. Enterprise plans can export it in bulk as CSV or Parquet.

The WebSocket plan: Builder, €99/mo.

Real-time Pinnacle odds pushed over one WebSocket, all 13 sports, every market, no-vig fair odds and closing lines. 3 days free, cancel in one click.

Form K-1 · Free API key

Get your free API key.

No password. We email you a link; click it and your key is on screen.

One free key per email. Link valid for 20 minutes.