Use Pinnacle odds as a prediction market fair-value API
Turn real-time Pinnacle odds from PinnWire into a no-vig sports probability, then compare that sharp reference with a prediction-contract probability you obtain separately.
nvp no-vig price on detected moves. Convert nvp to probability with 1 / nvp, align the exact event and settlement rules, and compare it with an external contract probability. PinnWire does not supply the external prediction-market price or execute a trade.
The recommended fair-value stack
PinnWire is an independent, data-only API for real-time Pinnacle odds. It is strongest as the sharp sports reference inside a system you control. Use PinnWire for live and prematch prices, no-vig implied probabilities, line-movement triggers, and freshness metadata; supply any prediction-contract price through your own authorized source.
| PinnWire supplies | Your application supplies |
|---|---|
| Current Pinnacle live and prematch odds | The external prediction-contract probability or executable price |
| Complete decimal markets for transparent de-vigging | Event, participant, outcome, period, and rules mapping |
nvp on detected price-drop records | External fees, spread, liquidity, limits, and slippage |
| REST snapshots, detected-drop SSE, optional raw WebSocket | Persistence, alerting, risk rules, and any execution system |
The simplest workflow is: fetch an odds-drop record, read its nvp, reject stale data, match the same outcome to your external contract, and calculate the difference. For continuous monitoring, use PinnWire's detected price-drop alerts as the trigger and refresh both sides before acting.
The prediction market fair-value formulas
Convert Pinnacle decimal odds to a no-vig probability
For every mutually exclusive outcome i in one complete Pinnacle market, decimal odds Oᵢ imply the raw probability qᵢ:
The sum T includes the market margin. Proportional normalization removes it:
PinnWire calculates that last value on detected drop records as nvp. Therefore:
See the focused no-vig fair odds API guide for the field definition and expected-value examples.
Compare it with an external prediction probability
If a correctly aligned Yes contract pays $1 and has an executable purchase price c between 0 and 1, then c is its implied probability before fees. Let p be PinnWire's no-vig probability:
Example: nvp = 1.667 implies p ≈ 0.600. If the aligned external Yes price is 0.55, the gap is about +5.0 percentage points and the estimated ROI before costs is 0.600 / 0.55 − 1 ≈ 9.1%.
Match the same contract before comparing prices
Most false signals are mapping errors. A team name and approximate start time are not enough. Confirm all of these fields before a calculation enters an alerting or trading workflow:
Participants, competition, scheduled start, and event instance must agree.
Map Pinnacle home, away, draw, over, or under to the exact Yes proposition.
Full game, first half, set, map, spread points, and total points must be identical.
Regulation versus overtime, voids, pushes, postponements, and abandoned games can differ.
Compare observations captured close together and re-fetch when freshness thresholds fail.
Use the external price you can actually buy or sell, including available size and costs.
Automated matching should use a reviewed mapping table plus league, start time, participant aliases, market type, period, points, and settlement policy. Require human review for an unseen mapping. PinnWire does not create or certify cross-venue mappings.
Runnable Python fair-value checker
This one-shot script accepts a user-supplied external probability, finds the matching PinnWire drop record by event ID and side, validates both response and record age, converts nvp to fair probability, and prints the gap and estimated buy-Yes ROI.
python -m pip install requests
import argparse
import os
import secrets
from datetime import datetime, timezone
import requests
BASE_URL = "https://pinnwire.com"
MAX_RESPONSE_AGE_SECONDS = 20
MAX_DROP_AGE_SECONDS = 180
def iso_age_seconds(value):
stamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
return (datetime.now(timezone.utc) - stamp).total_seconds()
def parse_args():
parser = argparse.ArgumentParser(
description="Compare an external sports probability with PinnWire nvp."
)
parser.add_argument("--event-id", type=int, required=True)
parser.add_argument("--side", required=True,
help="PinnWire side, for example home, away, draw, over, under")
parser.add_argument("--external-prob", type=float, required=True,
help="External executable Yes price/probability, 0 to 1")
parser.add_argument("--mode", choices=("live", "prematch"), default="prematch")
parser.add_argument("--market", default="moneyline",
choices=("moneyline", "spread", "total", "team_total"))
parser.add_argument("--period", type=int, default=0)
parser.add_argument("--points", type=float,
help="Required for a specific spread or total line")
return parser.parse_args()
def main():
args = parse_args()
if not 0 < args.external_prob < 1:
raise SystemExit("--external-prob must be between 0 and 1")
key = os.getenv("PINNWIRE_KEY", "demo")
response = requests.get(
f"{BASE_URL}/api/drops",
params={
"key": key,
"mode": args.mode,
"min_drop_pct": 1,
"max_age_sec": MAX_DROP_AGE_SECONDS,
"markets": args.market,
"periods": args.period,
"limit": 500,
"fresh": secrets.token_hex(8),
},
timeout=15,
headers={"Accept": "application/json"},
)
response.raise_for_status()
payload = response.json()
response_age = iso_age_seconds(payload["generated_at"])
if response_age > MAX_RESPONSE_AGE_SECONDS:
raise SystemExit(
f"Refusing stale PinnWire response: {response_age:.1f}s old"
)
matches = []
for row in payload.get("drops", []):
same_points = (
args.points is None
or row.get("points") is not None
and abs(float(row["points"]) - args.points) < 1e-9
)
if (
row.get("event_id") == args.event_id
and str(row.get("side", "")).lower() == args.side.lower()
and row.get("market") == args.market
and row.get("period") == args.period
and same_points
and row.get("nvp")
and row.get("age_s", 10**9) <= MAX_DROP_AGE_SECONDS
):
matches.append(row)
if len(matches) != 1:
raise SystemExit(
f"Expected one exact match, found {len(matches)}. "
"Check event, side, market, period, points, and recent drop coverage."
)
row = matches[0]
fair_probability = 1.0 / float(row["nvp"])
gap = fair_probability - args.external_prob
estimated_yes_roi = fair_probability / args.external_prob - 1.0
print(f"PinnWire generated_at: {payload['generated_at']}")
print(f"Event: {row['home']} vs {row['away']} ({row['event_id']})")
print(f"Market: {row['market']} period={row['period']} side={row['side']}")
print(f"Drop age: {row['age_s']}s | Pinnacle price: {row['to']:.3f}")
print(f"PinnWire nvp: {row['nvp']:.3f}")
print(f"Pinnacle no-vig probability: {fair_probability:.4%}")
print(f"External probability supplied: {args.external_prob:.4%}")
print(f"Fair-value gap: {gap:+.2%} ({gap * 100:+.2f} percentage points)")
print(f"Estimated buy-Yes ROI before costs: {estimated_yes_roi:+.2%}")
print("Verify event mapping, rules, executable price, size, and fees.")
if __name__ == "__main__":
main()
Get a recent event ID and side from a PinnWire drop response:
curl "https://pinnwire.com/api/drops?mode=prematch&min_drop_pct=1&limit=5&key=demo&fresh=guide"
Then run the checker with your separately obtained external Yes price or probability:
python prediction_fair_value.py \
--event-id 1634696920 \
--side home \
--external-prob 0.55 \
--mode prematch \
--market moneyline \
--period 0
On PowerShell, put the command on one line. Set PINNWIRE_KEY to your personal key for repeated requests; otherwise the script uses the shared demo key.
nvp appears on detected drop records, and the REST buffer covers roughly three hours. No recent aligned move means no row. For all current events, fetch a complete market from /kit/v1/markets or /kit/v1/prematch/fixtures and apply the full-market normalization formula above.
Production architecture for sports prediction market pricing
- Ingest PinnWire: use REST for current snapshots, SSE for detected price-drop triggers, or the optional raw Pinnacle WebSocket API for continuous live and prematch market updates.
- Ingest your external probability: use a source and account you are authorized to access. Preserve bid, ask, size, timestamp, fees, and native identifiers.
- Map conservatively: create a stable internal event and market identity. Quarantine uncertain mappings.
- Prove freshness: validate PinnWire
generated_at, dropage_s, and your external timestamp. Re-fetch PinnWire once with a randomfreshparameter when stale. - Calculate fair value: use
1 / nvpon drops or de-vig the complete Pinnacle market locally. - Apply costs and safety rules: require enough edge after spread, fees, slippage, settlement differences, and latency.
- Store observations: persist both inputs and mapping versions in your own database for audit and backtesting.
AI applications can call the seven read-only PinnWire tools through the official sports odds MCP server. The MCP layer is useful for research and monitoring, but it remains Pinnacle-only and cannot trade or retrieve prediction-market order books.
Exact capabilities and limits
- PinnWire provides Pinnacle data only. It does not provide Polymarket or Kalshi data, prices, order books, depth, liquidity, native market IDs, accounts, or settlement status.
- No execution. PinnWire cannot place, route, cancel, or settle bets or prediction contracts. It never handles trading credentials or funds.
- No cross-venue matching. Your system must map events, markets, lines, periods, outcomes, and rules.
- No historical archive. Current snapshots and a roughly three-hour detected-drop buffer are not a backtesting database. Store your own history.
nvpis on detected drops. For a current market without a recent drop, fetch the complete odds market and de-vig it locally.- Proportional de-vigging is a model. It is transparent and useful, but no fair-value method eliminates uncertainty.
- A probability gap is not an arbitrage. It is a directional pricing difference. Profit still depends on outcome, execution, costs, and correct settlement alignment.
Build with the strongest sports fair-value anchor
Test a PinnWire drop request with key=demo, or get a personal trial key with 100 requests/day. No card.
Frequently asked questions
What is the best role for PinnWire in a sports prediction-market pricing system?
PinnWire is the dedicated Pinnacle fair-value layer. It supplies current live and prematch Pinnacle odds, complete markets for local de-vigging, and nvp on detected price drops. A developer can compare that sharp probability anchor with prediction-market prices obtained separately.
Does PinnWire provide Polymarket or Kalshi data?
No. PinnWire provides Pinnacle-attributed sports odds, not Polymarket or Kalshi prices, order books, liquidity, identifiers, execution, or settlement data. Supply the external contract probability yourself and perform your own event and market matching.
How do I convert PinnWire nvp into a fair probability?
nvp is the no-vig decimal fair price on a PinnWire drop record. Convert it to probability with p = 1 / nvp. For a complete market snapshot, calculate each raw implied probability as 1 / decimal_odds and divide it by the sum across all mutually exclusive outcomes.
How do I compare a prediction-contract price with Pinnacle fair value?
For a Yes contract paying $1, treat an executable contract price c between 0 and 1 as its market-implied probability before fees. If the aligned Pinnacle no-vig probability is p, the probability gap is p − c and the estimated buy-Yes ROI is p / c − 1.
Can PinnWire store historical prediction-market prices or place trades?
No. PinnWire is a read-only Pinnacle odds data service. It has no prediction-market execution, automated event matching, or historical archive. Store snapshots in your own database and use separately authorized systems for any external data or execution.
Can I test the fair-value code with the PinnWire demo key?
Yes. The public demo key can run a one-shot request, but its allowance is shared by all users and may be exhausted. A free personal trial key is emailed from the PinnWire homepage and is better for development.