> ## 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

> Build a Python alert system that tracks NBA player props line movements. Detect when lines shift, find value, and get notified via Discord or email.

# 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:

| Trigger      | Example                               | Speed         |
| ------------ | ------------------------------------- | ------------- |
| Injury news  | "LeBron listed as questionable"       | Minutes       |
| Sharp money  | High-volume bets on one side          | 30-60 minutes |
| Public money | Gradual movement toward popular picks | Hours         |

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

```mermaid theme={null}
graph LR
    A["Python Poller"] -->|every 60s| B["FieldFunded API"]
    B -->|player props| A
    A --> C{"Line moved?"}
    C -->|yes| D["Discord / Email Alert"]
    C -->|no| E["Store snapshot"]
    D --> F["CSV log for analysis"]
    E --> F
```

## Prerequisites

```bash theme={null}
pip install requests pandas
```

* Python 3.8+
* Free API key from [fieldfunded.com/docs](https://fieldfunded.com/docs)

## Step 1: Fetch Player Props for a Game

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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:

```python theme={null}
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

```python theme={null}
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

```python theme={null}
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

| Scenario                       | Games | Requests/cycle | Cycles/day | Monthly | Plan       |
| ------------------------------ | ----- | -------------- | ---------- | ------- | ---------- |
| 3 NBA games, 60s poll, 4 hours | 3     | 3              | 240        | 21,600  | Free       |
| Full NBA slate (8 games), 60s  | 8     | 8              | 1,440      | 345,600 | Pro (\$59) |
| NBA + soccer (15 events), 60s  | 15    | 15             | 1,440      | 648,000 | Pro (\$59) |
| Hobby: 2 games, 5-min poll     | 2     | 2              | 48         | 2,880   | Free       |

Player props are included in the standard odds response — no additional API calls or charges. The [free tier](/compare/free-sports-api) 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](/compare/settlement-api) to automatically check if your bets on these movements won after the game.

***

## Related Guides

* [Build a Player Props Tracker](/guides/build-player-props-tracker) — display player props with live odds updates
* [Build a Line Tracker](/guides/build-line-tracker) — track odds movements across all market types
* [Analyze Sports Data with Pandas](/blog/sports-data-analysis-python) — data analysis techniques
* [Build a Discord Bot](/guides/build-discord-bot) — receive alerts as Discord slash commands
* [Settlement API Overview](/compare/settlement-api) — settle player prop bets automatically

<CardGroup cols={2}>
  <Card title="Get Your Free API Key" icon="key" href="/quickstart">
    Player props included on all plans — 10,000 free requests/month
  </Card>

  <Card title="See Pricing" icon="dollar-sign" href="/compare/pricing">
    All plans compared side by side
  </Card>
</CardGroup>
