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:
- Timeseries Mode: OHLC data for a single market selection with time windows
- 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 windows1h- 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
| Parameter | Type | Required | Description |
|---|---|---|---|
marketSelectionId | string | Yes | Market selection ID (MRKT_xxx) |
timeseriesStart | string | No | ISO timestamp for window start |
timeseriesEnd | string | No | ISO timestamp for window end |
rollup | string | No | Time window: 5m, 15m, 1h, 1d (default: 1h) |
Summary Mode
| Parameter | Type | Required | Description |
|---|---|---|---|
player OR team | string | Yes | Player or team ID |
eventStartTimeStart | string | Yes | ISO timestamp for event range start |
eventStartTimeEnd | string | No | ISO timestamp for event range end |
proposition | string | No | Filter by market type |
position | string | No | Filter by position (over/under/home/away) |
type | string | No | Filter by type |
pageNum | number | No | Page number for pagination |
pageSize | number | No | Results per page (default: 100) |
Need Help?
- ๐ง Support: [email protected]
- ๐ API Reference: /reference
- ๐ฌ Discord: Join our developer community
Updated 4 days ago