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

# Build a Betting Odds Line Movement Tracker

> Build a line movement tracker that detects sharp money and steam moves. Poll odds, store snapshots, visualize trends, and alert on shifts.

# Build a Line Movement Tracker That Spots Sharp Money

Track how odds change over time to detect sharp money movement and steam moves. This guide uses the FieldFunded SDK to poll odds, store historical snapshots, and alert on significant shifts.

## What You'll Use

| SDK Method       | Endpoint                   | Purpose                           |
| ---------------- | -------------------------- | --------------------------------- |
| `getEvents()`    | `GET /v1/events`           | List upcoming events with odds    |
| `getEventOdds()` | `GET /v1/events/{id}/odds` | Get current odds for an event     |
| `getEvent()`     | `GET /v1/events/{id}`      | Get event detail with all markets |
| `getLive()`      | `GET /v1/live`             | Track live events                 |

## Architecture

```
Poll getEventOdds() every N minutes
       ↓
Store snapshot in database (timestamp, event_id, market, odds)
       ↓
Compare with previous snapshot
       ↓
If change > threshold → Alert (Discord webhook, email)
```

## Step 1: Set Up

```bash theme={null}
npm install @fieldfunded/sdk better-sqlite3 node-cron
```

```typescript theme={null}
import { FieldFundedSDK } from '@fieldfunded/sdk';
import Database from 'better-sqlite3';
import cron from 'node-cron';

const client = new FieldFundedSDK({
  apiKey: process.env.FIELDFUNDED_API_KEY!,
  baseUrl: 'https://api.fieldfunded.com/v1',
});

const db = new Database('odds_history.db');

// Create snapshots table
db.exec(`
  CREATE TABLE IF NOT EXISTS odds_snapshots (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
    event_id TEXT,
    home_team TEXT,
    away_team TEXT,
    market_name TEXT,
    selection_name TEXT,
    odds REAL,
    UNIQUE(timestamp, event_id, market_name, selection_name)
  )
`);
```

## Step 2: Snapshot Odds

```typescript theme={null}
async function snapshotOdds(sport: string = 'soccer') {
  const events = await client.getEvents({
    sport,
    status: 'prematch',
    starts_within: '24h',
  });

  for (const event of events.events.slice(0, 20)) {
    const detail = await client.getEvent(event.id);

    if (!detail.markets) continue;

    // Track main markets (1x2, Over/Under, BTTS)
    const mainMarkets = detail.markets.filter((m: any) =>
      ['Match Winner', '1X2', 'Over/Under 2.5', 'Both Teams to Score'].includes(m.name)
    );

    const insert = db.prepare(`
      INSERT OR IGNORE INTO odds_snapshots
      (event_id, home_team, away_team, market_name, selection_name, odds)
      VALUES (?, ?, ?, ?, ?, ?)
    `);

    for (const market of mainMarkets) {
      for (const selection of market.selections) {
        insert.run(
          event.id,
          event.home_team,
          event.away_team,
          market.name,
          selection.name,
          selection.odds
        );
      }
    }
  }
}
```

## Step 3: Detect Movement

```typescript theme={null}
function detectMovement(threshold: number = 0.05) {
  const movements = db.prepare(`
    SELECT
      s1.event_id,
      s1.home_team,
      s1.away_team,
      s1.market_name,
      s1.selection_name,
      s1.odds as current_odds,
      s2.odds as previous_odds,
      (s1.odds - s2.odds) as change,
      ABS(s1.odds - s2.odds) / s2.odds as pct_change
    FROM odds_snapshots s1
    JOIN odds_snapshots s2 ON
      s1.event_id = s2.event_id AND
      s1.market_name = s2.market_name AND
      s1.selection_name = s2.selection_name AND
      s1.id > s2.id
    WHERE ABS(s1.odds - s2.odds) / s2.odds > ?
    ORDER BY s1.timestamp DESC
    LIMIT 20
  `).all(threshold);

  return movements;
}
```

## Step 4: Alert via Discord Webhook

```typescript theme={null}
async function sendAlert(movement: any) {
  const direction = movement.change > 0 ? '📈' : '📉';
  const color = movement.change > 0 ? 0x00FF00 : 0xFF0000;

  await fetch(process.env.DISCORD_WEBHOOK_URL!, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      embeds: [{
        title: `${direction} Line Movement Detected`,
        color,
        fields: [
          { name: 'Match', value: `${movement.home_team} vs ${movement.away_team}`, inline: false },
          { name: 'Market', value: `${movement.market_name} — ${movement.selection_name}`, inline: false },
          { name: 'Previous', value: `${movement.previous_odds}`, inline: true },
          { name: 'Current', value: `${movement.current_odds}`, inline: true },
          { name: 'Change', value: `${(movement.pct_change * 100).toFixed(1)}%`, inline: true },
        ],
      }],
    }),
  });
}
```

## Step 5: Schedule

```typescript theme={null}
// Snapshot every 10 minutes (144 req/day for 20 events ≈ 4,320/month)
cron.schedule('*/10 * * * *', async () => {
  await snapshotOdds('soccer');
  const movements = detectMovement(0.05); // 5% threshold

  for (const m of movements) {
    await sendAlert(m);
  }
});
```

## Rate Limit Math

| Polling frequency | Events tracked | Requests/day | Monthly (free tier?) |
| ----------------- | -------------- | ------------ | -------------------- |
| Every 10 min      | 20 events      | \~288        | \~8,640 ✅            |
| Every 5 min       | 20 events      | \~576        | \~17,280 (Starter)   |
| Every 2 min       | 50 events      | \~3,600      | \~108,000 (Pro)      |

The free tier (10K/month) supports tracking 20 events every 10 minutes.

<Card title="Events API Reference" icon="calendar" href="/api-reference/events/get-events">
  See events endpoint filters →
</Card>

***

## Related Guides

* [Sports Prediction with ML](/guides/sports-prediction-model) — use odds data as ML features
* [Build a Betting Platform](/guides/build-betting-platform) — full-stack architecture
* [See All Alternatives](/compare/alternatives) — compare odds data providers

<CardGroup cols={2}>
  <Card title="Get Your Free API Key" icon="key" href="/quickstart">
    Start tracking lines today — 10,000 free requests/month
  </Card>

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