I've been working on a bot that records the result of matches on SaltyBet.com. It uses that data to calculate the probability of winning for fighters of a given match. A fighters probability is based on the Elo rating system. I'm posting this here to share the code.
Repo: https://github.com/zakarh/salt-bot
import json
import selenium
from selenium import webdriver
from selenium.common import exceptions
from selenium.webdriver.common.keys import Keys
class SaltBot():
def __init__(self):
self.data = {}
def watch(self, browser, source="data.json", output="data.json", matches=1):
"""
Watch matches, make predictions, and record the results.
:type browser: selenium.webDriver, Example: selenium.webdriver.Chrome().
:type source: str, directory to source file.
:type target: str, directory to output file.
:type matches: int, No. matches to watch.
"""
if type(browser) != selenium.webdriver.chrome.webdriver.WebDriver:
raise TypeError(
"type(browser) != selenium.webdriver.chrome.webdriver.WebDriver, type(browser) == {}".format(type(browser)))
if type(source) != str:
raise TypeError(
"type(source) != str, type(source) == {}".format(type(source)))
if type(output) != str:
raise TypeError(
"type(output) != str, type(output) == {}".format(type(output)))
if type(matches) != int:
raise TypeError(
"type(matches) != int, type(matches) == {}".format(type(matches)))
if matches <= 0:
raise ValueError(
"matches <= 0, matches == {}, matches must be > 0".format(matches))
# Load data from source:
self.load_data(source)
# Navigate to SaltyBet.com using the browser.
url_address = "https://www.saltybet.com/"
browser.get(url_address)
print("Salt Bot is now watching {} matches.\n".format(matches))
# Handle operations performed in the while loop:
bet_status = "" # Handle control flow.
recorded = False # Decide when to record the result of matches.
red = "" # Fighter.
blue = "" # Fighter.
match = 0
print("{}/{} matches watched.".format(match, matches))
while match <= matches:
try:
# Get bet status:
current_bet_status = browser.find_element_by_id(
"betstatus").text
if len(current_bet_status) == 0:
continue
elif bet_status == "":
bet_status = current_bet_status
elif bet_status != current_bet_status:
bet_status = current_bet_status
except exceptions.StaleElementReferenceException:
pass
except Exception:
pass
try:
# Get fighter names:
red_current = browser.find_element_by_id(
"sbettors1").find_element_by_tag_name("strong").text
blue_current = browser.find_element_by_id(
"sbettors2").find_element_by_tag_name("strong").text
# Calculate probability for fighters:
if len(red_current) == 0 or len(blue_current) == 0:
continue
elif red == "" or blue == "":
red = self.extract_name(red_current)
blue = self.extract_name(blue_current)
self.display(red, blue)
elif red != red_current or blue != blue_current:
red = self.extract_name(red_current)
blue = self.extract_name(blue_current)
self.display(red, blue)
except exceptions.StaleElementReferenceException:
pass
except Exception:
pass
# Record the results of a match.
try:
if "win" in bet_status:
if recorded is False:
if red in bet_status:
s = "Winner: {}, Loser: {}\n".format(red, blue)
print(s)
self.save_data("data.json", red, blue)
self.log_data(s)
elif blue in bet_status:
s = "Winner: {}, Loser: {}\n".format(blue, red)
print(s)
self.save_data("data.json", blue, red)
self.log_data(s)
recorded = True
match += 1
print(
"{}/{} matches watched.".format(match, matches))
else:
recorded = False
except exceptions.StaleElementReferenceException:
pass
except Exception:
pass
def get_probability(self, red, blue):
"""
Calculate the probability of red and blue winning the match using the Elo Rating System.
:type red: str, name of fighter.
:type blue: str, name of fighter.
:rtype: tuple, respective probability of red and blue winning the match.
TODO: Account for win:loss ratio of red's opponents against blue vice versa.
"""
# Validate input:
if type(red) != str:
raise TypeError("type(red) != str")
if type(blue) != str:
raise TypeError("type(blue) != str")
# Insufficient data return (0.5, 0.5)
if red not in self.data or blue not in self.data:
return (0.5, 0.5)
else:
# Compute the sum of matches won by both fighters:
red_rating = sum([self.data[red][loser]
for loser in self.data[red]])
blue_rating = sum([self.data[blue][loser]
for loser in self.data[blue]])
# Decremenet fighter rating by their total losses against the other fighter:
if red in self.data[blue]:
red_rating -= self.data[blue][red]
if blue in self.data[red]:
blue_rating -= self.data[red][blue]
# Transform fighter rating:
red_rating = pow(10, max(0, red_rating) / 400)
blue_rating = pow(10, max(0, blue_rating) / 400)
# Calculate red and blue's probability of winning:
red_probability = red_rating / (red_rating + blue_rating)
blue_probability = blue_rating / (red_rating + blue_rating)
return (red_probability, blue_probability)
def save_data(self, file_path, winner, loser):
"""
Open the target file and update the winner and loser data.
:type file_path: str, directory path to target file.
:type winner: str, name of fighter.
:type loser: str, name of fighter.
"""
# Validate input:
if type(file_path) != str:
raise TypeError("type(file_path) != str")
if type(winner) != str:
raise TypeError("type(winner) != str")
if type(loser) != str:
raise TypeError("type(loser) != str")
# Open the target file and write the results.
with open(file=file_path, mode="w+") as f:
# Add winner and loser if they don't exist in self.data:
self.data.setdefault(winner, {})
self.data.setdefault(loser, {})
self.data[winner].setdefault(loser, 0)
self.data[loser].setdefault(winner, 0)
# Increment the number of wins winner has against the loser:
self.data[winner][loser] += 1
f.write(json.dumps(self.data))
def load_data(self, file_path):
"""
Open the target file and load its contents as JSON.
:type file_path: str, directory path to target file.
:rtype: None
"""
# Validate input:
if type(file_path) != str:
raise TypeError("type(file_path) != str")
# Open the target file and load contents as json.
with open(file=file_path, mode="r+") as f:
data = json.load(f)
self.data = data
def log_data(self, data):
file_path = "data.txt"
with open(file=file_path, mode="a+") as f:
f.write(data)
def extract_name(self, raw_name):
return str(raw_name).strip(" ")
def display(self, red, blue):
s = "\n{} vs. {}\n{}".format(
red, blue, self.get_probability(red, blue))
print(s)
def main():
sb = SaltBot()
sb.watch(browser=webdriver.Chrome(), source="data.json",
output="data.json", matches=100)
if __name__ == '__main__':
main()