Historic Odds & Lines

Track line movement and analyze historic pricing data

Historic Odds & Lines

Track how betting lines move over time with comprehensive historic pricing data and OHLC timeseries for charting and analysis.

Endpoint

GET /prices/historic

Access historic pricing data in two modes:

  1. Timeseries Mode: OHLC data for a single market selection with time windows
  2. Summary Mode: Multi-event price summary for player/team across date range

Timeseries Mode

Get OHLC (Open, High, Low, Close) price data for charting and line movement analysis.

Basic Usage

import requests
from datetime import datetime, timedelta

base_url = "https://api.sharpsports.io/v1"
headers = {"Authorization": "Token sk_test_your_key_here"}

# Get 24 hours of price data
now = datetime.utcnow()
yesterday = now - timedelta(days=1)

response = requests.get(
    f"{base_url}/prices/historic",
    headers=headers,
    params={
        "marketSelectionId": "MRKT_def456abc789012345678901234567",
        "timeseriesStart": yesterday.isoformat() + "Z",
        "timeseriesEnd": now.isoformat() + "Z",
        "rollup": "1h"
    }
)

timeseries = response.json()

Response Structure

[
  {
    "timestamp": "2024-01-15T12:00:00Z",
    "open": 26.5,
    "high": 27.0,
    "low": 26.0,
    "close": 26.5,
    "rollup": "1h"
  },
  {
    "timestamp": "2024-01-15T13:00:00Z",
    "open": 26.5,
    "high": 27.5,
    "low": 26.5,
    "close": 27.0,
    "rollup": "1h"
  }
]

Rollup Options

Control the time window size:

  • 5m - 5-minute windows (high frequency, short-term movements)
  • 15m - 15-minute windows
  • 1h - 1-hour windows (default, good for daily tracking)
  • 1d - Daily windows (long-term trends)
# 5-minute windows for detailed analysis
detailed = requests.get(
    f"{base_url}/prices/historic",
    headers=headers,
    params={
        "marketSelectionId": mrkt_id,
        "timeseriesStart": start_time,
        "timeseriesEnd": end_time,
        "rollup": "5m"
    }
).json()

# Daily windows for long-term trends
daily = requests.get(
    f"{base_url}/prices/historic",
    headers=headers,
    params={
        "marketSelectionId": mrkt_id,
        "timeseriesStart": start_time,
        "timeseriesEnd": end_time,
        "rollup": "1d"
    }
).json()

Summary Mode

Get multi-event price summaries for analyzing player/team pricing trends across multiple games.

Basic Usage

# Get price summary for player across date range
response = requests.get(
    f"{base_url}/prices/historic",
    headers=headers,
    params={
        "player": "PLYR_abc123def456789012345678901234",
        "eventStartTimeStart": "2024-01-01T00:00:00Z",
        "eventStartTimeEnd": "2024-01-31T23:59:59Z",
        "proposition": "player_points",
        "position": "over"
    }
)

summaries = response.json()

Response Structure

[
  {
    "marketSelectionId": "MRKT_abc123",
    "event": {
      "id": "EVNT_xyz789",
      "name": "Lakers @ Warriors",
      "startTime": "2024-01-15T19:00:00Z"
    },
    "player": {"id": "PLYR_abc123", "fullName": "LeBron James"},
    "proposition": "player_points",
    "position": "over",
    "bestPrice": {
      "line": 26.5,
      "odds": -110,
      "book": {"abbr": "dk", "name": "DraftKings"}
    },
    "consensusLine": 26.5,
    "lineRange": {"min": 25.5, "max": 27.5}
  }
]

Filtering Options

# Filter by team
team_prices = requests.get(
    f"{base_url}/prices/historic",
    headers=headers,
    params={
        "team": "TEAM_lakers",
        "eventStartTimeStart": start_date,
        "eventStartTimeEnd": end_date,
        "proposition": "spread"
    }
).json()

# Filter by market type
totals = requests.get(
    f"{base_url}/prices/historic",
    headers=headers,
    params={
        "player": player_id,
        "eventStartTimeStart": start_date,
        "eventStartTimeEnd": end_date,
        "proposition": "player_rebounds"
    }
).json()

Line Movement Analysis

Track Opening to Closing

def get_line_movement(market_selection_id):
    """Analyze line movement from open to close"""
    # Get full timeseries
    now = datetime.utcnow()
    week_ago = now - timedelta(days=7)

    timeseries = requests.get(
        f"{base_url}/prices/historic",
        headers=headers,
        params={
            "marketSelectionId": market_selection_id,
            "timeseriesStart": week_ago.isoformat() + "Z",
            "timeseriesEnd": now.isoformat() + "Z",
            "rollup": "1h"
        }
    ).json()

    if not timeseries:
        return None

    # Opening and closing lines
    opening = timeseries[-1]['open']
    closing = timeseries[0]['close']
    movement = closing - opening

    # Find highest and lowest
    all_highs = [w['high'] for w in timeseries]
    all_lows = [w['low'] for w in timeseries]

    peak = max(all_highs)
    bottom = min(all_lows)

    print(f"Opening Line: {opening:.1f}")
    print(f"Current Line: {closing:.1f}")
    print(f"Movement: {movement:+.1f}")
    print(f"Peak: {peak:.1f}")
    print(f"Bottom: {bottom:.1f}")
    print(f"Range: {peak - bottom:.1f}")

    return {
        "opening": opening,
        "closing": closing,
        "movement": movement,
        "peak": peak,
        "bottom": bottom
    }

get_line_movement("MRKT_def456abc789012345678901234567")

Detect Steam Moves

def detect_steam_moves(market_selection_id, threshold=1.0):
    """Detect sudden line movements (steam)"""
    now = datetime.utcnow()
    day_ago = now - timedelta(days=1)

    # Get hourly data
    timeseries = requests.get(
        f"{base_url}/prices/historic",
        headers=headers,
        params={
            "marketSelectionId": market_selection_id,
            "timeseriesStart": day_ago.isoformat() + "Z",
            "timeseriesEnd": now.isoformat() + "Z",
            "rollup": "1h"
        }
    ).json()

    steam_moves = []

    for i in range(len(timeseries) - 1):
        current = timeseries[i]
        previous = timeseries[i + 1]

        # Calculate movement
        movement = current['close'] - previous['close']

        if abs(movement) >= threshold:
            steam_moves.append({
                "timestamp": current['timestamp'],
                "from": previous['close'],
                "to": current['close'],
                "movement": movement,
                "direction": "UP" if movement > 0 else "DOWN"
            })

    print(f"Detected {len(steam_moves)} steam moves:")
    for move in steam_moves:
        print(f"  {move['timestamp']}: {move['from']:.1f} โ†’ {move['to']:.1f} ({move['movement']:+.1f})")

    return steam_moves

detect_steam_moves("MRKT_def456abc789012345678901234567", threshold=1.0)

Compare Across Games

def compare_player_lines(player_id, proposition="player_points", games=10):
    """Compare player's prop lines across recent games"""
    # Get last 30 days
    end = datetime.utcnow()
    start = end - timedelta(days=30)

    summaries = requests.get(
        f"{base_url}/prices/historic",
        headers=headers,
        params={
            "player": player_id,
            "proposition": proposition,
            "eventStartTimeStart": start.isoformat() + "Z",
            "eventStartTimeEnd": end.isoformat() + "Z"
        }
    ).json()

    # Sort by event start time
    summaries.sort(key=lambda x: x['event']['startTime'], reverse=True)

    print(f"Last {min(games, len(summaries))} games - {proposition}:")
    print("-" * 60)

    for summary in summaries[:games]:
        event_date = summary['event']['startTime'][:10]
        opponent = summary['event']['name'].split('@')[1].strip()
        line = summary.get('consensusLine', 'N/A')

        print(f"{event_date} vs {opponent:15} Line: {line}")

    # Calculate average
    lines = [s['consensusLine'] for s in summaries[:games] if 'consensusLine' in s]
    avg_line = sum(lines) / len(lines) if lines else 0

    print(f"\nAverage Line (L{len(lines)}): {avg_line:.1f}")

    return summaries

compare_player_lines("PLYR_abc123def456789012345678901234", "player_points", games=10)

Complete Examples

Line Movement Dashboard

def create_line_dashboard(market_selection_id):
    """Comprehensive line movement analysis"""
    now = datetime.utcnow()
    week_ago = now - timedelta(days=7)

    # Get timeseries data
    timeseries = requests.get(
        f"{base_url}/prices/historic",
        headers=headers,
        params={
            "marketSelectionId": market_selection_id,
            "timeseriesStart": week_ago.isoformat() + "Z",
            "timeseriesEnd": now.isoformat() + "Z",
            "rollup": "1h"
        }
    ).json()

    if not timeseries:
        print("No historic data available")
        return

    print("\n" + "="*60)
    print("LINE MOVEMENT DASHBOARD")
    print("="*60)

    # Opening/Closing
    opening = timeseries[-1]['open']
    current = timeseries[0]['close']
    movement = current - opening

    print(f"\n๐Ÿ“Š Summary:")
    print(f"  Opening Line: {opening:.1f}")
    print(f"  Current Line: {current:.1f}")
    print(f"  Total Movement: {movement:+.1f}")

    # Direction
    if abs(movement) >= 1.0:
        direction = "โ†‘ SIGNIFICANT UP" if movement > 0 else "โ†“ SIGNIFICANT DOWN"
        print(f"  Direction: {direction}")
    elif abs(movement) >= 0.5:
        direction = "โ†‘ Moderate up" if movement > 0 else "โ†“ Moderate down"
        print(f"  Direction: {direction}")
    else:
        print(f"  Direction: โ†’ Stable")

    # Volatility
    all_lines = []
    for window in timeseries:
        all_lines.extend([window['open'], window['high'], window['low'], window['close']])

    line_range = max(all_lines) - min(all_lines)
    print(f"  Volatility (range): {line_range:.1f}")

    # Recent movement (last 3 hours)
    recent = timeseries[:3]
    recent_movement = recent[0]['close'] - recent[-1]['open']

    print(f"\n๐Ÿ”„ Recent Movement (3h):")
    print(f"  Change: {recent_movement:+.1f}")

    for window in recent[:5]:
        timestamp = datetime.fromisoformat(window['timestamp'].replace('Z', '+00:00'))
        time_str = timestamp.strftime("%I:%M%p")
        print(f"  {time_str}: {window['open']:.1f} โ†’ {window['close']:.1f}")

create_line_dashboard("MRKT_def456abc789012345678901234567")

Player Line Trends

def analyze_player_trends(player_id, proposition="player_points"):
    """Analyze how a player's prop lines have trended over time"""
    # Get last 60 days
    end = datetime.utcnow()
    start = end - timedelta(days=60)

    summaries = requests.get(
        f"{base_url}/prices/historic",
        headers=headers,
        params={
            "player": player_id,
            "proposition": proposition,
            "eventStartTimeStart": start.isoformat() + "Z",
            "eventStartTimeEnd": end.isoformat() + "Z"
        }
    ).json()

    summaries.sort(key=lambda x: x['event']['startTime'])

    print(f"\nPlayer Line Trends - {proposition}")
    print("-" * 60)

    # Calculate trends
    lines = [s['consensusLine'] for s in summaries if 'consensusLine' in s]

    if len(lines) >= 5:
        first_5_avg = sum(lines[:5]) / 5
        last_5_avg = sum(lines[-5:]) / 5
        overall_avg = sum(lines) / len(lines)

        print(f"Overall Average: {overall_avg:.1f}")
        print(f"First 5 Games: {first_5_avg:.1f}")
        print(f"Last 5 Games: {last_5_avg:.1f}")
        print(f"Trend: {last_5_avg - first_5_avg:+.1f}")

        if last_5_avg > first_5_avg + 1.0:
            print("๐Ÿ“ˆ Lines trending UP (improving performance)")
        elif last_5_avg < first_5_avg - 1.0:
            print("๐Ÿ“‰ Lines trending DOWN (declining performance)")
        else:
            print("โ†’ Lines relatively stable")

    # Show recent games
    print(f"\nRecent Games:")
    for summary in summaries[-10:]:
        date = summary['event']['startTime'][:10]
        line = summary.get('consensusLine', 'N/A')
        print(f"  {date}: {line}")

analyze_player_trends("PLYR_abc123def456789012345678901234", "player_points")

Query Parameters Reference

Timeseries Mode

ParameterTypeRequiredDescription
marketSelectionIdstringYesMarket selection ID (MRKT_xxx)
timeseriesStartstringNoISO timestamp for window start
timeseriesEndstringNoISO timestamp for window end
rollupstringNoTime window: 5m, 15m, 1h, 1d (default: 1h)

Summary Mode

ParameterTypeRequiredDescription
player OR teamstringYesPlayer or team ID
eventStartTimeStartstringYesISO timestamp for event range start
eventStartTimeEndstringNoISO timestamp for event range end
propositionstringNoFilter by market type
positionstringNoFilter by position (over/under/home/away)
typestringNoFilter by type
pageNumnumberNoPage number for pagination
pageSizenumberNoResults per page (default: 100)

Need Help?