Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.fieldfunded.com/llms.txt

Use this file to discover all available pages before exploring further.

Track NBA Player Props Line Movements with Python

Player props are the fastest-moving pre-match lines in sports betting. When a star player is listed as questionable, or when sharp money hits a line, the odds shift within minutes — hours before tipoff. This tutorial builds a Python system that monitors NBA player prop lines before games start, detects significant movements, and sends you alerts so you can act before the market corrects.

Why Player Props Move

Lines move for three main reasons:
TriggerExampleSpeed
Injury news”LeBron listed as questionable”Minutes
Sharp moneyHigh-volume bets on one side30-60 minutes
Public moneyGradual movement toward popular picksHours
All three happen pre-match — typically 1-4 hours before tipoff. This is when monitoring is most valuable, because lines are still adjusting to new information and the market is least efficient. Pre-match prop lines offer significantly more edge and +EV opportunities than live lines, because live odds are algorithmically adjusted in real-time and leave almost no exploitable advantage. Pre-match lines, on the other hand, react slower to breaking news — giving you a window to find genuine edge before the market corrects. The FieldFunded API provides pre-match player props across all major sports, refreshed every 300ms.

What You’ll Build

Prerequisites

pip install requests pandas

Step 1: Fetch Player Props for a Game

import requests

API_KEY = "your_api_key_here"
BASE = "https://api.fieldfunded.com/v1"
H = {"X-API-Key": API_KEY}

def get_player_props(event_id):
    """Fetch all player prop markets for a given event."""
    odds = requests.get(
        f"{BASE}/events/{event_id}/odds", headers=H
    ).json()

    prop_keywords = [
        "player", "goalscorer", "points", "rebounds",
        "assists", "threes", "steals", "blocks",
        "passing", "rushing", "receiving"
    ]

    props = []
    for market in odds.get("markets", []):
        name_lower = market["name"].lower()
        if any(kw in name_lower for kw in prop_keywords):
            for sel in market["selections"]:
                props.append({
                    "market": market["name"],
                    "market_id": market["id"],
                    "selection": sel["name"],
                    "selection_id": sel["id"],
                    "odds": sel["odds"],
                })

    return props

# Example: get props for a specific NBA game
# props = get_player_props("event_abc123")
# print(f"Found {len(props)} player prop lines")

Step 2: Find NBA Games with Props

def get_nba_events():
    """Get today's NBA events."""
    events = requests.get(
        f"{BASE}/events",
        headers=H,
        params={"sport": "basketball_nba"}
    ).json()

    return events.get("events", [])

# Print today's games
nba = get_nba_events()
for game in nba:
    print(f"{game['home_team']} vs {game['away_team']}")
    print(f"  ID: {game['id']}")
    print(f"  Kickoff: {game['commence_time']}")

Step 3: Build the Line Movement Detector

import time
from datetime import datetime

class PropMonitor:
    def __init__(self, threshold_pct=3.0):
        self.baseline = {}  # key -> first odds seen
        self.previous = {}  # key -> last odds seen
        self.threshold = threshold_pct
        self.alerts = []

    def check_event(self, event_id, event_name):
        """Check all player props for an event and detect movements."""
        props = get_player_props(event_id)

        for prop in props:
            key = f"{event_id}_{prop['market_id']}_{prop['selection_id']}"
            current = prop["odds"]

            # Set baseline on first scan
            if key not in self.baseline:
                self.baseline[key] = current
                self.previous[key] = current
                continue

            prev = self.previous[key]
            base = self.baseline[key]

            # Detect movement from previous scan
            if prev != current:
                change_pct = ((current - prev) / prev) * 100
                from_base_pct = ((current - base) / base) * 100

                if abs(change_pct) >= self.threshold:
                    alert = {
                        "time": datetime.now().isoformat(),
                        "event": event_name,
                        "market": prop["market"],
                        "selection": prop["selection"],
                        "previous": prev,
                        "current": current,
                        "change_pct": round(change_pct, 1),
                        "from_baseline_pct": round(from_base_pct, 1),
                        "direction": "DRIFTED" if change_pct > 0
                                     else "SHORTENED",
                    }
                    self.alerts.append(alert)
                    self._print_alert(alert)

            self.previous[key] = current

    def _print_alert(self, a):
        arrow = "↑" if a["direction"] == "DRIFTED" else "↓"
        print(
            f"\n{arrow} [{a['direction']}] {a['event']}\n"
            f"  {a['market']}{a['selection']}\n"
            f"  {a['previous']}{a['current']} "
            f"({a['change_pct']:+.1f}% from last, "
            f"{a['from_baseline_pct']:+.1f}% from baseline)"
        )

Step 4: Add Discord Notifications

def send_discord_alert(webhook_url, alert):
    """Send a line movement alert to Discord."""
    color = 0x53fc18 if alert["direction"] == "DRIFTED" else 0xef4444
    arrow = "📈" if alert["direction"] == "DRIFTED" else "📉"

    embed = {
        "title": f"{arrow} {alert['direction']}: {alert['selection']}",
        "color": color,
        "fields": [
            {"name": "Event", "value": alert["event"], "inline": False},
            {"name": "Market", "value": alert["market"], "inline": True},
            {"name": "Movement",
             "value": f"{alert['previous']}{alert['current']}",
             "inline": True},
            {"name": "Change",
             "value": f"{alert['change_pct']:+.1f}%",
             "inline": True},
            {"name": "From Baseline",
             "value": f"{alert['from_baseline_pct']:+.1f}%",
             "inline": True},
        ],
        "timestamp": alert["time"],
    }

    requests.post(webhook_url, json={"embeds": [embed]})
Integrate it into the monitor:
class PropMonitor:
    def __init__(self, threshold_pct=3.0, discord_webhook=None):
        self.baseline = {}
        self.previous = {}
        self.threshold = threshold_pct
        self.webhook = discord_webhook
        self.alerts = []

    def _print_alert(self, a):
        # ... (same as above)

        # Send Discord notification
        if self.webhook:
            send_discord_alert(self.webhook, a)

Step 5: Run the Full Monitor

import os

DISCORD_WEBHOOK = os.environ.get("DISCORD_WEBHOOK")
POLL_INTERVAL = 60  # seconds

monitor = PropMonitor(
    threshold_pct=3.0,
    discord_webhook=DISCORD_WEBHOOK,
)

print("=== NBA Player Props Monitor ===")
print(f"Threshold: {monitor.threshold}%")
print(f"Poll interval: {POLL_INTERVAL}s\n")

while True:
    nba_events = get_nba_events()
    print(f"[{datetime.now().strftime('%H:%M:%S')}] "
          f"Scanning {len(nba_events)} NBA games...")

    for event in nba_events:
        name = f"{event['home_team']} vs {event['away_team']}"
        monitor.check_event(event["id"], name)

        # Rate limit protection
        time.sleep(0.3)

    time.sleep(POLL_INTERVAL)
Output:
=== NBA Player Props Monitor ===
Threshold: 3.0%
Poll interval: 60s

[20:15:30] Scanning 6 NBA games...

↓ [SHORTENED] Lakers vs Celtics
  Player Points O/U — LeBron James Over 25.5
  2.10 → 1.85 (-11.9% from last, -11.9% from baseline)

↑ [DRIFTED] Nuggets vs Timberwolves
  Player Rebounds O/U — Nikola Jokic Over 12.5
  1.75 → 1.95 (+11.4% from last, +11.4% from baseline)

Step 6: Export Data for Analysis

import pandas as pd

def export_alerts(monitor, filename="prop_alerts.csv"):
    """Save all detected movements to CSV."""
    if not monitor.alerts:
        print("No alerts to export")
        return

    df = pd.DataFrame(monitor.alerts)
    df.to_csv(filename, index=False)
    print(f"Exported {len(df)} alerts to {filename}")

    # Quick summary
    print(f"\nTop movers:")
    top = df.sort_values("change_pct", key=abs, ascending=False).head(5)
    for _, row in top.iterrows():
        print(f"  {row['selection']}: {row['change_pct']:+.1f}%")

# Call after monitoring session
# export_alerts(monitor)

Rate Limit Math

ScenarioGamesRequests/cycleCycles/dayMonthlyPlan
3 NBA games, 60s poll, 4 hours3324021,600Free
Full NBA slate (8 games), 60s881,440345,600Pro ($59)
NBA + soccer (15 events), 60s15151,440648,000Pro ($59)
Hobby: 2 games, 5-min poll22482,880Free
Player props are included in the standard odds response — no additional API calls or charges. The free tier handles casual monitoring.

Practical Tips

  • Set the threshold to 3-5% for actionable alerts. Below 3% generates too much noise from normal market fluctuations.
  • Focus on the 2-4 hour window before tipoff. This is when injury reports drop and sharp money moves the lines most.
  • Pre-match props are where the edge is. Live odds are adjusted algorithmically and are extremely efficient — pre-match lines lag behind news, creating genuine +EV windows and exploitable edge that can last minutes to hours.
  • Player prop lines adjust faster than match winner lines to news. A “questionable” tag on a star player can move his points O/U by 10-15% within an hour.
  • Combine with the settlement API to automatically check if your bets on these movements won after the game.

Get Your Free API Key

Player props included on all plans — 10,000 free requests/month

See Pricing

All plans compared side by side