Change to spaces for indentation
This commit is contained in:
100
logic.py
100
logic.py
@@ -6,74 +6,74 @@ BASE_URL = "https://api.the-odds-api.com/v4"
|
|||||||
|
|
||||||
|
|
||||||
class AuthenticationException(RuntimeError):
|
class AuthenticationException(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class RateLimitException(RuntimeError):
|
class RateLimitException(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def handle_faulty_response(response: requests.Response):
|
def handle_faulty_response(response: requests.Response):
|
||||||
if response.status_code == 401:
|
if response.status_code == 401:
|
||||||
raise AuthenticationException("Failed to authenticate with the API. is the API key valid?")
|
raise AuthenticationException("Failed to authenticate with the API. is the API key valid?")
|
||||||
elif response.status_code == 429:
|
elif response.status_code == 429:
|
||||||
raise RateLimitException("Encountered API rate limit.")
|
raise RateLimitException("Encountered API rate limit.")
|
||||||
|
|
||||||
|
|
||||||
def get_sports(key: str) -> set[str]:
|
def get_sports(key: str) -> set[str]:
|
||||||
url = f"{BASE_URL}/sports/"
|
url = f"{BASE_URL}/sports/"
|
||||||
querystring = {"apiKey": key}
|
querystring = {"apiKey": key}
|
||||||
|
|
||||||
response = requests.get(url, params=querystring)
|
response = requests.get(url, params=querystring)
|
||||||
if not response:
|
if not response:
|
||||||
handle_faulty_response(response)
|
handle_faulty_response(response)
|
||||||
|
|
||||||
return {item["group"] for item in response.json()}
|
return {item["group"] for item in response.json()}
|
||||||
|
|
||||||
|
|
||||||
def get_data(key: str, sport: str, region: str = "eu"):
|
def get_data(key: str, sport: str, region: str = "eu"):
|
||||||
url = f"{BASE_URL}/sports/{sport}/odds/"
|
url = f"{BASE_URL}/sports/{sport}/odds/"
|
||||||
querystring = {
|
querystring = {
|
||||||
"apiKey": key,
|
"apiKey": key,
|
||||||
"regions": region,
|
"regions": region,
|
||||||
"oddsFormat": "decimal",
|
"oddsFormat": "decimal",
|
||||||
"dateFormat": "unix"
|
"dateFormat": "unix"
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.get(url, params=querystring)
|
response = requests.get(url, params=querystring)
|
||||||
if not response:
|
if not response:
|
||||||
handle_faulty_response(response)
|
handle_faulty_response(response)
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
def process_data(matches: Iterable, include_started_matches: bool = True) -> Generator[dict, None, None]:
|
def process_data(matches: Iterable, include_started_matches: bool = True) -> Generator[dict, None, None]:
|
||||||
"""Extracts all matches that are available and calculates some things about them, such as the time to start and
|
"""Extracts all matches that are available and calculates some things about them, such as the time to start and
|
||||||
the best available implied odds."""
|
the best available implied odds."""
|
||||||
for match in matches:
|
for match in matches:
|
||||||
start_time = int(match["commence_time"])
|
start_time = int(match["commence_time"])
|
||||||
if not include_started_matches and start_time < time.time():
|
if not include_started_matches and start_time < time.time():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
best_odd_per_outcome = {}
|
best_odd_per_outcome = {}
|
||||||
for bookmaker in match["bookmakers"]:
|
for bookmaker in match["bookmakers"]:
|
||||||
bookie_name = bookmaker["title"]
|
bookie_name = bookmaker["title"]
|
||||||
for outcome in bookmaker["markets"][0]["outcomes"]:
|
for outcome in bookmaker["markets"][0]["outcomes"]:
|
||||||
outcome_name = outcome["name"]
|
outcome_name = outcome["name"]
|
||||||
odd = outcome["price"]
|
odd = outcome["price"]
|
||||||
if outcome_name not in best_odd_per_outcome.keys() or \
|
if outcome_name not in best_odd_per_outcome.keys() or \
|
||||||
odd > best_odd_per_outcome[outcome_name][1]:
|
odd > best_odd_per_outcome[outcome_name][1]:
|
||||||
best_odd_per_outcome[outcome_name] = (bookie_name, odd)
|
best_odd_per_outcome[outcome_name] = (bookie_name, odd)
|
||||||
|
|
||||||
total_implied_odds = sum(1/i[1] for i in best_odd_per_outcome.values())
|
total_implied_odds = sum(1/i[1] for i in best_odd_per_outcome.values())
|
||||||
match_name = f"{match['home_team']} v. {match['away_team']}"
|
match_name = f"{match['home_team']} v. {match['away_team']}"
|
||||||
time_to_start = (start_time - time.time())/3600
|
time_to_start = (start_time - time.time())/3600
|
||||||
league = match["sport_key"]
|
league = match["sport_key"]
|
||||||
yield {
|
yield {
|
||||||
"match_name": match_name,
|
"match_name": match_name,
|
||||||
"match_start_time": start_time,
|
"match_start_time": start_time,
|
||||||
"hours_to_start": time_to_start,
|
"hours_to_start": time_to_start,
|
||||||
"league": league,
|
"league": league,
|
||||||
"best_outcome_odds": best_odd_per_outcome,
|
"best_outcome_odds": best_odd_per_outcome,
|
||||||
"total_implied_odds": total_implied_odds,
|
"total_implied_odds": total_implied_odds,
|
||||||
}
|
}
|
||||||
96
main.py
96
main.py
@@ -11,60 +11,60 @@ from rich import print
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="Arbitrage Finder",
|
prog="Arbitrage Finder",
|
||||||
description=__doc__
|
description=__doc__
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-k", "--key",
|
|
||||||
default=os.environ.get("API_KEY"),
|
|
||||||
help="The API key from The Odds API. If left blank it will default to the value of $API_KEY."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-r", "--region",
|
|
||||||
choices=["eu", "us", "au", "uk"],
|
|
||||||
default="eu",
|
|
||||||
help="The region in which to look for arbitrage opportunities."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-u", "--unformatted",
|
|
||||||
action="store_true",
|
|
||||||
help="If set, turn output into the json dump from the opportunities."
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-c", "--cutoff",
|
|
||||||
type=float,
|
|
||||||
default=0,
|
|
||||||
help="The minimum profit margin required for an arb to be displayed. Inputted as a percentage."
|
|
||||||
)
|
)
|
||||||
args = parser.parse_args()
|
parser.add_argument(
|
||||||
|
"-k", "--key",
|
||||||
|
default=os.environ.get("API_KEY"),
|
||||||
|
help="The API key from The Odds API. If left blank it will default to the value of $API_KEY."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-r", "--region",
|
||||||
|
choices=["eu", "us", "au", "uk"],
|
||||||
|
default="eu",
|
||||||
|
help="The region in which to look for arbitrage opportunities."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-u", "--unformatted",
|
||||||
|
action="store_true",
|
||||||
|
help="If set, turn output into the json dump from the opportunities."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-c", "--cutoff",
|
||||||
|
type=float,
|
||||||
|
default=0,
|
||||||
|
help="The minimum profit margin required for an arb to be displayed. Inputted as a percentage."
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
key = args.key
|
key = args.key
|
||||||
region = args.region
|
region = args.region
|
||||||
print_unformatted = args.unformatted
|
print_unformatted = args.unformatted
|
||||||
cutoff = args.cutoff/100
|
cutoff = args.cutoff/100
|
||||||
|
|
||||||
# logic
|
# logic
|
||||||
sports = get_sports(key)
|
sports = get_sports(key)
|
||||||
data = chain.from_iterable(get_data(key, sport, region=region) for sport in sports)
|
data = chain.from_iterable(get_data(key, sport, region=region) for sport in sports)
|
||||||
data = filter(lambda x: x != "message", data)
|
data = filter(lambda x: x != "message", data)
|
||||||
results = process_data(data)
|
results = process_data(data)
|
||||||
arbitrage_opportunities = filter(lambda x: x["total_implied_odds"] < 1-cutoff, results)
|
arbitrage_opportunities = filter(lambda x: x["total_implied_odds"] < 1-cutoff, results)
|
||||||
|
|
||||||
if print_unformatted:
|
if print_unformatted:
|
||||||
print(arbitrage_opportunities)
|
print(arbitrage_opportunities)
|
||||||
else:
|
else:
|
||||||
arbitrage_opportunities = list(arbitrage_opportunities)
|
arbitrage_opportunities = list(arbitrage_opportunities)
|
||||||
print(f"{len(arbitrage_opportunities)} arbitrage opportunities found {':money-mouth_face:' if len(arbitrage_opportunities) > 0 else ':man_shrugging:'}")
|
print(f"{len(arbitrage_opportunities)} arbitrage opportunities found {':money-mouth_face:' if len(arbitrage_opportunities) > 0 else ':man_shrugging:'}")
|
||||||
|
|
||||||
for arb in arbitrage_opportunities:
|
for arb in arbitrage_opportunities:
|
||||||
print(f"\t[italic]{arb['match_name']} in {arb['league']} [/italic]")
|
print(f"\t[italic]{arb['match_name']} in {arb['league']} [/italic]")
|
||||||
print(f"\t\tTotal implied odds: {arb['total_implied_odds']} with these odds:")
|
print(f"\t\tTotal implied odds: {arb['total_implied_odds']} with these odds:")
|
||||||
for key, value in arb['best_outcome_odds'].items():
|
for key, value in arb['best_outcome_odds'].items():
|
||||||
print(f"\t\t[bold red]{key}[/bold red] with [green]{value[0]}[/green] for {value[1]}")
|
print(f"\t\t[bold red]{key}[/bold red] with [green]{value[0]}[/green] for {value[1]}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
Reference in New Issue
Block a user