On the wire · 05:08:42 UTC Live 585 · Prematch 14,345 · Markets 57,062 Vol. I · No. 5 · Friday 13 November 2026 wss://pinnfeed.io/v1/stream
PinnFeed
Tutorial · Data · DuckDB

Closing odds Pinnacle in DuckDB e Parquet

Aggiornato il · PinnFeed · 1 min di lettura

Risposta: su Builder o Pro, esegui un piccolo job giornaliero che pagina GET /v1/closing?date=YYYY-MM-DD (fino a 2.000 righe per chiamata), appiattisce market e outcomes, e scrive un Parquet al giorno. DuckDB interroga poi la cartella in SQL, senza server database. I record esistono da quando PinnFeed archivia le closing.

1. Daily export (Python)#

import os, sys, requests, pandas as pd
H = {"x-api-key": os.environ["PINNFEED_KEY"]}  # ps_live_...

def closing_day(day, sport):
    rows, offset = [], 0
    while True:
        r = requests.get("https://pinnfeed.io/v1/closing", headers=H, timeout=30,
                         params={"date": day, "sport": sport, "limit": 2000, "offset": offset})
        r.raise_for_status()                     # 403 plan_lacks_history on Free/Hobby
        batch = r.json()["closing"]
        rows += batch
        if len(batch) < 2000:
            return rows
        offset += 2000

day = sys.argv[1]                                # e.g. 2026-11-08
out = []
for row in closing_day(day, "soccer"):
    m = row["market"]
    for o in m["outcomes"] or []:
        out.append({
            "event_id": row["eventId"], "league": row["league"]["name"], "home": row["home"], "away": row["away"],
            "start_time": row["startTime"], "captured_at": row["capturedAt"],
            "market": m["type"], "period": m["period"], "line": m["line"], "main": m["main"],
            "margin": m["margin"], "max_stake": m["maxStake"],
            "outcome": o.get("name"), "decimal": o.get("decimal"), "fair": o.get("fair"),
        })
os.makedirs("closing", exist_ok=True)
pd.DataFrame(out).to_parquet(f"closing/soccer-{day}.parquet", index=False)   # needs pyarrow
print(len(out), "outcome rows")

2. Query with DuckDB#

-- duckdb shell, from the folder that contains closing/
SELECT league, avg(margin) AS avg_margin
FROM read_parquet('closing/*.parquet')
GROUP BY 1
ORDER BY 2 DESC
LIMIT 20;

CTA#

Builder (99 €). 3-day trial; card required. Free REST: signup. Related: closing/CLV, backtesting.

Domande frequenti#

Free/Hobby bastano?#

No — /v1/closing restituisce 403 plan_lacks_history fuori Builder/Pro.

Contenuto educativo per adulti?#

Sì (18+).

Contenuto educativo per maggiorenni (18+): non pronostici né promesse di guadagno. In Italia scommetti solo con operatori autorizzati ADM.

Il piano WebSocket: Builder, 99 €/mese.

Quote Pinnacle in tempo reale su un unico WebSocket: tutti i 13 sport, tutti i mercati, quote eque senza margine e quote di chiusura. 3 giorni gratis, disdici con un clic.

Form K-1 · Free API key

Get your free API key.

One field. No password, no card: enter your email, click the link we send, and your key is on screen.

By continuing you agree to the Terms and Privacy Policy.

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