Module rating.rating.chessmetrics

Classes

class ChessMetrics (default_change=850, too_old=730, epsilon=0.01, home_advantage=20, delta_expected=2, weighted_average=0.99, **kwargs)

The ChessMetrics rating system implementation. See https://en.wikipedia.org/wiki/Chessmetrics Also partially based on https://www.kaggle.com/competitions/chess/discussion/193.

We adjust the rating system to ensure it always converges. For this purpose, we perform several iterations of the process described in the Wikipedia page over all games with a decay factor (weighted_average).

This class represents the ChessMetrics rating system used to calculate the ratings of chess players. It inherits from the RatingSystem class.

Attributes

  • default_change (float): The default rating change value.
  • too_old (int): The number of days after which a game is considered too old.
  • epsilon (float): The max change allowed until convergence.
  • home_advantage (int): The advantage given to the home player.
  • delta_expected (float): The weighting to do in the logistic curve formula.
  • weighted_average (float): The weighted_average coefficient used in updated the ratings.

Args

  • default_change (float, optional): The default change value. Defaults to 850.
  • too_old (int, optional): The number of days after which a game is considered too old. Defaults to 2 years.
  • epsilon (float, optional): The max change allowed until convergence. Defaults to 50.
  • home_advantage (int, optional): The advantage given to the home player. Defaults to 20.
  • delta_expected (float, optional): The weighting to do in the logistic curve formula. Defaults to 2.
  • weighted_average (float, optional): The weighted_average coefficient used in updated the ratings. Defaults to 0.9.
Expand source code
class ChessMetrics(RatingSystem):
    def __init__(self, default_change=850, too_old=2 * 365, epsilon=1e-2, 
                 home_advantage=20, delta_expected=2, weighted_average=0.99, **kwargs) -> 'ChessMetrics':
        """
        The ChessMetrics rating system implementation. See https://en.wikipedia.org/wiki/Chessmetrics
        Also partially based on https://www.kaggle.com/competitions/chess/discussion/193.

        We adjust the rating system to ensure it always converges. For this purpose, we perform several
        iterations of the process described in the Wikipedia page over all games with a decay factor (weighted_average). 

        This class represents the ChessMetrics rating system used to calculate the ratings of chess players.
        It inherits from the `RatingSystem` class.

        Attributes:
            - default_change (float): The default rating change value.
            - too_old (int): The number of days after which a game is considered too old.
            - epsilon (float): The max change allowed until convergence.
            - home_advantage (int): The advantage given to the home player.
            - delta_expected (float): The weighting to do in the logistic curve formula.
            - weighted_average (float): The weighted_average coefficient used in updated the ratings.

        Args:
            - default_change (float, optional): The default change value. Defaults to 850.
            - too_old (int, optional): The number of days after which a game is considered too old. Defaults to 2 years.
            - epsilon (float, optional): The max change allowed until convergence. Defaults to 50.
            - home_advantage (int, optional): The advantage given to the home player. Defaults to 20.
            - delta_expected (float, optional): The weighting to do in the logistic curve formula. Defaults to 2.
            - weighted_average (float, optional): The weighted_average coefficient used in updated the ratings. Defaults to 0.9.
        """
        super().__init__(**kwargs, default_change=default_change, too_old=too_old, epsilon=epsilon, 
                         home_advantage=home_advantage, delta_expected=delta_expected, 
                         weighted_average=weighted_average)
        self.too_old_timedelta = timedelta(days=self.too_old)

    def compute_weights(self, games : List[Game], last_period_date : datetime):
        """
        Computes the weights of the games.

        Args:
            - games (list): A list of game objects.
            - last_period_date (datetime): The last date of the period.
        
        Returns:
            - list: A list of weights for the games.
        """
        weights = []
        for game in games:
            date = game.get_date()
            age = (last_period_date - date).days
            weight = max(0, self.too_old - age) / self.too_old
            weights.append(game.weight * weight)
        return weights

    def period_update(self, player_database : PlayerDatabase, 
                      game_database : GameDatabase, 
                      period_dates : List[datetime], **kwargs):
        games = list(game_database.get_games_between_dates(period_dates[-1], period_dates[-1] - self.too_old_timedelta))
        weights = self.compute_weights(games, period_dates[-1])
        ratings, old_ratings = [], []
        for id in range(player_database.get_max_id() + 1):
            player = player_database[id]
            if player is None:
                ratings.append(0)
                old_ratings.append(0)
            else:
                ratings.append(player.get_rating().default_rating.rating)
                old_ratings.append(player.get_rating().default_rating.rating)
        
        while True:
            player_averages = [(0, 0) for _ in range(player_database.get_max_id() + 1)]
            for game, weight in zip(games, weights):
                result = game.get_result()

                player_averages[game.home] = (player_averages[game.home][0] + weight * (ratings[game.out] - self.home_advantage + (result - 0.5) * self.default_change), 
                                            player_averages[game.home][1] + weight)
                player_averages[game.out] = (player_averages[game.out][0] + weight * (ratings[game.home] + self.home_advantage - (result - 0.5) * self.default_change), 
                                            player_averages[game.out][1] + weight)
            for i in range(len(player_averages)):
                ratings[i] *= (1 - self.weighted_average)
                if player_averages[i][1] > 0:
                    ratings[i] += self.weighted_average * player_averages[i][0] / player_averages[i][1]
                else:
                    ratings[i] += self.weighted_average * ratings[i]
            if np.max(np.abs(np.array(ratings) - np.array(old_ratings))) < self.epsilon:
                break
            old_ratings = ratings[:]

        for player in player_database:
            player.get_rating().update(ratings[player.id])

    def compute_expected_score_rating(self, rating : float, opponent_rating : float, is_home : bool = False) -> float:
        if is_home:
            return 1 / (1 + 10 ** (self.delta_expected * (opponent_rating.rating - rating.rating - 2 * self.home_advantage) / self.default_change))
        return 1 / (1 + 10 ** (self.delta_expected * (opponent_rating.rating - rating.rating   + 2 * self.home_advantage) / self.default_change))

Ancestors

Methods

def compute_weights(self, games: List[Game], last_period_date: datetime.datetime)

Computes the weights of the games.

Args

  • games (list): A list of game objects.
  • last_period_date (datetime): The last date of the period.

Returns

  • list: A list of weights for the games.

Inherited members