Skip to main content
2 of 2
Obvious typo in first indentation; fixed
Reinderien
  • 65.4k
  • 5
  • 70
  • 188

A bot for SaltyBet that watches and records matches

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()