sharpapi 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sharpapi-0.2.0/.github/workflows/publish.yml +27 -0
- sharpapi-0.2.0/.github/workflows/test.yml +28 -0
- sharpapi-0.2.0/.gitignore +12 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/PKG-INFO +7 -7
- {sharpapi-0.1.0 → sharpapi-0.2.0}/README.md +4 -4
- {sharpapi-0.1.0 → sharpapi-0.2.0}/pyproject.toml +5 -3
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/__init__.py +2 -2
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/_base.py +1 -3
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/client.py +1 -1
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/models.py +13 -5
- {sharpapi-0.1.0 → sharpapi-0.2.0}/tests/conftest.py +4 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/tests/test_async_client.py +1 -1
- {sharpapi-0.1.0 → sharpapi-0.2.0}/tests/test_client.py +2 -2
- {sharpapi-0.1.0 → sharpapi-0.2.0}/tests/test_dataframe.py +5 -5
- {sharpapi-0.1.0 → sharpapi-0.2.0}/tests/test_models.py +4 -4
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/_utils.py +0 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/async_client.py +0 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/exceptions.py +0 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/py.typed +0 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/src/sharpapi/streaming.py +0 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/tests/__init__.py +0 -0
- {sharpapi-0.1.0 → sharpapi-0.2.0}/tests/test_utils.py +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: "3.12"
|
|
19
|
+
|
|
20
|
+
- name: Install build tools
|
|
21
|
+
run: pip install hatch
|
|
22
|
+
|
|
23
|
+
- name: Build
|
|
24
|
+
run: hatch build
|
|
25
|
+
|
|
26
|
+
- name: Publish to PyPI
|
|
27
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: ${{ matrix.python-version }}
|
|
22
|
+
|
|
23
|
+
- name: Install dependencies
|
|
24
|
+
run: |
|
|
25
|
+
pip install -e ".[test,pandas]"
|
|
26
|
+
|
|
27
|
+
- name: Run tests
|
|
28
|
+
run: pytest -v
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sharpapi
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Official Python SDK for the SharpAPI real-time sports betting odds API
|
|
5
5
|
Project-URL: Homepage, https://sharpapi.io
|
|
6
6
|
Project-URL: Documentation, https://docs.sharpapi.io/sdks/python
|
|
7
|
-
Project-URL: Repository, https://github.com/
|
|
8
|
-
Project-URL: Changelog, https://github.com/
|
|
7
|
+
Project-URL: Repository, https://github.com/Sharp-API/sharpapi-python
|
|
8
|
+
Project-URL: Changelog, https://github.com/Sharp-API/sharpapi-python/releases
|
|
9
9
|
Author-email: SharpAPI <support@sharpapi.io>
|
|
10
10
|
License-Expression: MIT
|
|
11
11
|
Keywords: api,arbitrage,ev,odds,pinnacle,real-time,sports-betting
|
|
@@ -60,9 +60,9 @@ for arb in arbs.data:
|
|
|
60
60
|
# --- +EV opportunities ---
|
|
61
61
|
evs = client.ev.get(min_ev=3.0, sport="basketball")
|
|
62
62
|
for opp in evs.data:
|
|
63
|
-
print(f"+{opp.
|
|
64
|
-
if opp.
|
|
65
|
-
print(f" Kelly: {opp.
|
|
63
|
+
print(f"+{opp.ev_percentage:.1f}% EV on {opp.selection} @ {opp.sportsbook}")
|
|
64
|
+
if opp.kelly_percent:
|
|
65
|
+
print(f" Kelly: {opp.kelly_percent:.1%} of bankroll")
|
|
66
66
|
|
|
67
67
|
# --- Best odds across books ---
|
|
68
68
|
odds = client.odds.best(league="nba", market="moneyline")
|
|
@@ -80,7 +80,7 @@ stream = client.stream.opportunities(league="nba")
|
|
|
80
80
|
@stream.on("ev:detected")
|
|
81
81
|
def on_ev(data):
|
|
82
82
|
for opp in data:
|
|
83
|
-
print(f"+EV: {opp['selection']} {opp['
|
|
83
|
+
print(f"+EV: {opp['selection']} {opp['ev_percentage']}% @ {opp['sportsbook']}")
|
|
84
84
|
|
|
85
85
|
@stream.on("arb:detected")
|
|
86
86
|
def on_arb(data):
|
|
@@ -27,9 +27,9 @@ for arb in arbs.data:
|
|
|
27
27
|
# --- +EV opportunities ---
|
|
28
28
|
evs = client.ev.get(min_ev=3.0, sport="basketball")
|
|
29
29
|
for opp in evs.data:
|
|
30
|
-
print(f"+{opp.
|
|
31
|
-
if opp.
|
|
32
|
-
print(f" Kelly: {opp.
|
|
30
|
+
print(f"+{opp.ev_percentage:.1f}% EV on {opp.selection} @ {opp.sportsbook}")
|
|
31
|
+
if opp.kelly_percent:
|
|
32
|
+
print(f" Kelly: {opp.kelly_percent:.1%} of bankroll")
|
|
33
33
|
|
|
34
34
|
# --- Best odds across books ---
|
|
35
35
|
odds = client.odds.best(league="nba", market="moneyline")
|
|
@@ -47,7 +47,7 @@ stream = client.stream.opportunities(league="nba")
|
|
|
47
47
|
@stream.on("ev:detected")
|
|
48
48
|
def on_ev(data):
|
|
49
49
|
for opp in data:
|
|
50
|
-
print(f"+EV: {opp['selection']} {opp['
|
|
50
|
+
print(f"+EV: {opp['selection']} {opp['ev_percentage']}% @ {opp['sportsbook']}")
|
|
51
51
|
|
|
52
52
|
@stream.on("arb:detected")
|
|
53
53
|
def on_arb(data):
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sharpapi"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Official Python SDK for the SharpAPI real-time sports betting odds API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -36,8 +36,8 @@ test = ["pytest>=8.0", "pytest-asyncio>=0.23", "respx>=0.21"]
|
|
|
36
36
|
[project.urls]
|
|
37
37
|
Homepage = "https://sharpapi.io"
|
|
38
38
|
Documentation = "https://docs.sharpapi.io/sdks/python"
|
|
39
|
-
Repository = "https://github.com/
|
|
40
|
-
Changelog = "https://github.com/
|
|
39
|
+
Repository = "https://github.com/Sharp-API/sharpapi-python"
|
|
40
|
+
Changelog = "https://github.com/Sharp-API/sharpapi-python/releases"
|
|
41
41
|
|
|
42
42
|
[tool.hatch.build.targets.wheel]
|
|
43
43
|
packages = ["src/sharpapi"]
|
|
@@ -56,3 +56,5 @@ testpaths = ["tests"]
|
|
|
56
56
|
[tool.pyright]
|
|
57
57
|
pythonVersion = "3.9"
|
|
58
58
|
typeCheckingMode = "standard"
|
|
59
|
+
venvPath = "."
|
|
60
|
+
venv = ".venv"
|
|
@@ -14,7 +14,7 @@ Example::
|
|
|
14
14
|
# +EV opportunities
|
|
15
15
|
evs = client.ev.get(min_ev=3.0, league="nba")
|
|
16
16
|
for opp in evs.data:
|
|
17
|
-
print(f"+{opp.
|
|
17
|
+
print(f"+{opp.ev_percentage}% on {opp.selection} @ {opp.sportsbook}")
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
20
|
from .async_client import AsyncSharpAPI
|
|
@@ -51,7 +51,7 @@ from .models import (
|
|
|
51
51
|
from .streaming import EventStream
|
|
52
52
|
from ._utils import american_to_decimal, american_to_probability, decimal_to_american
|
|
53
53
|
|
|
54
|
-
__version__ = "0.
|
|
54
|
+
__version__ = "0.2.0"
|
|
55
55
|
|
|
56
56
|
__all__ = [
|
|
57
57
|
# Clients
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
5
|
import httpx
|
|
8
6
|
|
|
9
7
|
from .exceptions import (
|
|
@@ -17,7 +15,7 @@ from .models import APIResponse, RateLimitInfo, ResponseMeta
|
|
|
17
15
|
|
|
18
16
|
DEFAULT_BASE_URL = "https://api.sharpapi.io"
|
|
19
17
|
DEFAULT_TIMEOUT = 30.0
|
|
20
|
-
USER_AGENT = "sharpapi-python/0.
|
|
18
|
+
USER_AGENT = "sharpapi-python/0.2.0"
|
|
21
19
|
|
|
22
20
|
|
|
23
21
|
def parse_response(raw: dict, model_class: type) -> APIResponse:
|
|
@@ -52,7 +52,7 @@ class SharpAPI:
|
|
|
52
52
|
# Get +EV opportunities
|
|
53
53
|
evs = client.ev.get(min_ev=3.0, league="nba")
|
|
54
54
|
for opp in evs.data:
|
|
55
|
-
print(f"+{opp.
|
|
55
|
+
print(f"+{opp.ev_percentage}% on {opp.selection} @ {opp.sportsbook}")
|
|
56
56
|
|
|
57
57
|
# Stream real-time updates
|
|
58
58
|
stream = client.stream.opportunities(league="nba")
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from typing import Any, Generic, List, Optional, TypeVar
|
|
6
6
|
|
|
7
|
-
from pydantic import BaseModel, Field
|
|
7
|
+
from pydantic import AliasChoices, BaseModel, Field
|
|
8
8
|
|
|
9
9
|
T = TypeVar("T")
|
|
10
10
|
|
|
@@ -171,14 +171,22 @@ class EVOpportunity(BaseModel):
|
|
|
171
171
|
odds_american: int | float
|
|
172
172
|
odds_decimal: float
|
|
173
173
|
no_vig_odds: Optional[float] = None
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
174
|
+
fair_probability: Optional[float] = Field(
|
|
175
|
+
None, validation_alias=AliasChoices("fair_probability", "true_probability")
|
|
176
|
+
)
|
|
177
|
+
ev_percentage: float = Field(
|
|
178
|
+
validation_alias=AliasChoices("ev_percentage", "ev_percent")
|
|
179
|
+
)
|
|
180
|
+
kelly_percent: Optional[float] = Field(
|
|
181
|
+
None, validation_alias=AliasChoices("kelly_percent", "kelly_fraction")
|
|
182
|
+
)
|
|
177
183
|
confidence_score: Optional[float] = None
|
|
178
184
|
book_count: Optional[int] = None
|
|
179
185
|
market_width: Optional[float] = None
|
|
180
186
|
devig_method: Optional[str] = None
|
|
181
|
-
|
|
187
|
+
sharp_book: Optional[str] = Field(
|
|
188
|
+
None, validation_alias=AliasChoices("sharp_book", "devig_book")
|
|
189
|
+
)
|
|
182
190
|
sharp_odds_american: Optional[int | float] = None
|
|
183
191
|
sharp_odds_decimal: Optional[float] = None
|
|
184
192
|
line: Optional[float] = None
|
|
@@ -91,13 +91,17 @@ EV_RESPONSE = {
|
|
|
91
91
|
"odds_american": -105,
|
|
92
92
|
"odds_decimal": 1.952,
|
|
93
93
|
"no_vig_odds": 1.912,
|
|
94
|
+
"fair_probability": 0.523,
|
|
94
95
|
"true_probability": 0.523,
|
|
96
|
+
"ev_percentage": 4.2,
|
|
95
97
|
"ev_percent": 4.2,
|
|
98
|
+
"kelly_percent": 0.021,
|
|
96
99
|
"kelly_fraction": 0.021,
|
|
97
100
|
"confidence_score": 87,
|
|
98
101
|
"book_count": 8,
|
|
99
102
|
"market_width": 0.043,
|
|
100
103
|
"devig_method": "power",
|
|
104
|
+
"sharp_book": "pinnacle",
|
|
101
105
|
"devig_book": "pinnacle",
|
|
102
106
|
"is_live": False,
|
|
103
107
|
"possibly_stale": False,
|
|
@@ -174,7 +174,7 @@ class TestAsyncOpportunities:
|
|
|
174
174
|
result = await client.ev.get(min_ev=3.0)
|
|
175
175
|
assert len(result.data) == 1
|
|
176
176
|
assert isinstance(result.data[0], EVOpportunity)
|
|
177
|
-
assert result.data[0].
|
|
177
|
+
assert result.data[0].ev_percentage == 4.2
|
|
178
178
|
|
|
179
179
|
|
|
180
180
|
# =============================================================================
|
|
@@ -260,8 +260,8 @@ class TestEVResource:
|
|
|
260
260
|
assert len(result.data) == 1
|
|
261
261
|
ev = result.data[0]
|
|
262
262
|
assert isinstance(ev, EVOpportunity)
|
|
263
|
-
assert ev.
|
|
264
|
-
assert ev.
|
|
263
|
+
assert ev.ev_percentage == 4.2
|
|
264
|
+
assert ev.kelly_percent == 0.021
|
|
265
265
|
|
|
266
266
|
|
|
267
267
|
class TestMiddlesResource:
|
|
@@ -22,7 +22,7 @@ class TestToDataFrame:
|
|
|
22
22
|
result = parse_response(EV_RESPONSE, EVOpportunity)
|
|
23
23
|
df = result.to_dataframe()
|
|
24
24
|
assert len(df) == 1
|
|
25
|
-
assert df.iloc[0]["
|
|
25
|
+
assert df.iloc[0]["ev_percentage"] == 4.2
|
|
26
26
|
assert df.iloc[0]["sportsbook"] == "draftkings"
|
|
27
27
|
|
|
28
28
|
def test_flattens_nested_dicts(self):
|
|
@@ -60,24 +60,24 @@ class TestToDataFrame:
|
|
|
60
60
|
"id": "ev_1", "sport": "basketball", "league": "nba",
|
|
61
61
|
"selection": "A", "sportsbook": "dk",
|
|
62
62
|
"odds_american": -110, "odds_decimal": 1.909,
|
|
63
|
-
"
|
|
63
|
+
"ev_percentage": 4.2, "possibly_stale": False, "warnings": [],
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"id": "ev_2", "sport": "basketball", "league": "nba",
|
|
67
67
|
"selection": "B", "sportsbook": "fd",
|
|
68
68
|
"odds_american": 130, "odds_decimal": 2.3,
|
|
69
|
-
"
|
|
69
|
+
"ev_percentage": 2.8, "possibly_stale": False, "warnings": [],
|
|
70
70
|
},
|
|
71
71
|
],
|
|
72
72
|
}
|
|
73
73
|
result = parse_response(multi, EVOpportunity)
|
|
74
74
|
df = result.to_dataframe()
|
|
75
75
|
assert len(df) == 2
|
|
76
|
-
assert list(df["
|
|
76
|
+
assert list(df["ev_percentage"]) == [4.2, 2.8]
|
|
77
77
|
assert list(df["sportsbook"]) == ["dk", "fd"]
|
|
78
78
|
|
|
79
79
|
def test_dataframe_dtypes(self):
|
|
80
80
|
result = parse_response(EV_RESPONSE, EVOpportunity)
|
|
81
81
|
df = result.to_dataframe()
|
|
82
|
-
assert df["
|
|
82
|
+
assert df["ev_percentage"].dtype == "float64"
|
|
83
83
|
assert "str" in str(df["sportsbook"].dtype).lower() or df["sportsbook"].dtype == "object"
|
|
@@ -74,7 +74,7 @@ class TestEVModel:
|
|
|
74
74
|
result = parse_response(EV_RESPONSE, EVOpportunity)
|
|
75
75
|
assert len(result.data) == 1
|
|
76
76
|
ev = result.data[0]
|
|
77
|
-
assert ev.
|
|
77
|
+
assert ev.ev_percentage == 4.2
|
|
78
78
|
assert ev.selection == "PHO Suns"
|
|
79
79
|
assert ev.sportsbook == "draftkings"
|
|
80
80
|
assert ev.odds_american == -105
|
|
@@ -84,15 +84,15 @@ class TestEVModel:
|
|
|
84
84
|
result = parse_response(EV_RESPONSE, EVOpportunity)
|
|
85
85
|
ev = result.data[0]
|
|
86
86
|
assert ev.devig_method == "power"
|
|
87
|
-
assert ev.
|
|
87
|
+
assert ev.sharp_book == "pinnacle"
|
|
88
88
|
assert ev.no_vig_odds == 1.912
|
|
89
|
-
assert ev.
|
|
89
|
+
assert ev.fair_probability == 0.523
|
|
90
90
|
|
|
91
91
|
def test_scoring(self):
|
|
92
92
|
result = parse_response(EV_RESPONSE, EVOpportunity)
|
|
93
93
|
ev = result.data[0]
|
|
94
94
|
assert ev.confidence_score == 87
|
|
95
|
-
assert ev.
|
|
95
|
+
assert ev.kelly_percent == 0.021
|
|
96
96
|
assert ev.book_count == 8
|
|
97
97
|
|
|
98
98
|
def test_aliased_fields(self):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|