public RatingInfoDoubles.WeightingFactors CalculateMatchWeight(TeamInfo playerTeam, TeamInfo opponentTeam, Result r, int numPartnerResults) { /* * Match Weight is A x B x C x D * E * F * Where: * A = Opponent's Player Rating Reliability * B = Parter Frequency * C = Match Competitiveness Factor * D = Opponent Benchmark Reliability Factor * E = Interpool Reliability Factor * F = Team Rating Reliability */ var a = (_ratingRule.EnableMatchFormatReliability) ? CalculateMatchFormatReliabilityFactor(r) : 10.0f; var b = _ratingRule.EnablePartnerFrequencyReliability ? CalculatePartnerFrequency(numPartnerResults) : 1.0f; var c = (_ratingRule.EnableMatchCompetitivenessCoeffecient) ? MatchCompetivenessCalculator.CalculateMatchCompetivenessCoeffecient(playerTeam, opponentTeam, r, _ratingRule) : 1.0f; var d = 1.0f; // not yet used var e = (_ratingRule.EnableInterpoolCoeffecient) ? CalculateInterpoolCoefficient(playerTeam, opponentTeam) : 1.0f; var f = CalculateOpponentRatingReliabilityFactor(opponentTeam); var matchWeight = a * b * c * d * e * f; return(new RatingInfoDoubles.WeightingFactors { MatchFormatReliability = a, MatchFrequencyReliability = b, MatchCompetitivenessCoeffecient = c, BenchmarkMatchCoeffecient = d, OpponentRatingReliability = f, InterpoolCoeffecient = e, MatchWeight = Math.Truncate(100000 * matchWeight) / 100000 }); }
private float CalculateInterpoolCoefficient(TeamInfo playerTeam, TeamInfo opponentTeam) { // any diff in country ids var isForeignMatch = playerTeam.Player1CountryId != opponentTeam.Player1CountryId || playerTeam.Player1CountryId != opponentTeam.Player2CountryId || playerTeam.Player2CountryId != opponentTeam.Player2CountryId; if (playerTeam.HasCollegePlayer ^ opponentTeam.HasCollegePlayer) { return(_ratingRule.interpoolCoefficientCollege); } return(isForeignMatch ? _ratingRule.interpoolCoefficientCountry : 1.0f); }
public Result.MatchGender GetGenderType(TeamInfo team1, TeamInfo team2) { var genders = new List <string>() { team1.Player1Gender, team1.Player2Gender, team2.Player1Gender, team2.Player2Gender }; if (genders.Contains("M") && genders.Contains("F")) { return(Result.MatchGender.Coed); } if (genders.Contains("M")) { return(Result.MatchGender.Male); } if (genders.Contains("F")) { return(Result.MatchGender.Female); } throw new RatingException("Unable to determine result gender type"); }
// same thing but with team ratings public static float CalculateUnderdogMatch(TeamInfo playerTeam, TeamInfo opponentTeam, Result matchInfo, RatingRule rule) { TeamInfo winner, loser; if (matchInfo.Winner1Id == playerTeam.Player1Id || matchInfo.Winner1Id == playerTeam.Player2Id) { winner = playerTeam; loser = opponentTeam; } else { winner = opponentTeam; loser = playerTeam; } if (winner.TeamRating > loser.TeamRating && !CompetiveThresholdReached(matchInfo)) //If the expected team won and the loser didn't reach competitive threshold, it's expected. { return(rule.UnderDogMatchReliability); } else //Underdog was competitive { return(rule.CompetitiveUnderDogMatchReliability); } }
// same thing but with team ratings public static float CalculateMatchCompetivenessCoeffecient(TeamInfo playerTeam, TeamInfo opponentTeam, Result matchInfo, RatingRule rule) { float UTRDelta = (float)Math.Abs(playerTeam.TeamRating - opponentTeam.TeamRating), coeffecient = 1; if (UTRDelta <= rule.NormalMatchMaxUTRDelta && UTRDelta >= rule.CloseMatchMaxUTRDelta) //Normal match { coeffecient = DoCoeffcientCalculation(UTRDelta, rule); } else if (UTRDelta < rule.CloseMatchMaxUTRDelta) // Close Match { coeffecient = IsMatchLopsided(matchInfo, rule) ? rule.LopsidedMatchReliability : DoCoeffcientCalculation(UTRDelta, rule); //If a close match is lopsided, we assume one player must have been having an off game, reduced credit } else if (UTRDelta > rule.NormalMatchMaxUTRDelta) //Lopsided Match { coeffecient = CalculateUnderdogMatch(playerTeam, opponentTeam, matchInfo, rule); //Underdog matches, where player's have a large skill gap, calculate differently } else { throw new RatingException("Unable to determine competitiveness coeficcient for result id: " + matchInfo.Id); //coeffecient = 0.0f; //Fail safe, every match should be caught above, may want to throw exception here } return(coeffecient); }
public int GetPartnerResults(TeamInfo team, List <Result> results) { var count = 0; foreach (var r in results) { var team1Ids = new List <int> { r.Winner1Id, r.Winner2Id ?? 0 }; var team2Ids = new List <int> { r.Loser1Id, r.Loser2Id ?? 0 }; if (team1Ids.Contains(team.Player1Id) && team1Ids.Contains(team.Player2Id)) { count++; } if (team2Ids.Contains(team.Player1Id) && team2Ids.Contains(team.Player2Id)) { count++; } } return(count > 0 ? count : 1); }
public double CalculateDynamicRating(int playerId, TeamInfo playerTeam, TeamInfo opponentTeam, Result result) { // Effect of Partner-UTR-Spread on Team Adjustment Factor var matchPartnersDelta = playerTeam.RatingDiff - opponentTeam.RatingDiff; var baseline = CalculateMatchBaseline(playerTeam.TeamRating, opponentTeam.TeamRating); var teamAdjustment = CalculateTeamAdjustmentFactor(playerId, result, matchPartnersDelta, baseline, GetGenderType(playerTeam, opponentTeam)); var teamDynamicRating = CalculateTeamDynamicRating(baseline, teamAdjustment); // determine which position player is in if (playerId == playerTeam.Player1Id) { // calc dynamic rating as if partner rating was the same and cap there var playerTeamDynamicRating = CalculateTeamDynamicRating(CalculateMatchBaseline(playerTeam.Player1Rating, opponentTeam.TeamRating), teamAdjustment); var dynamicCap = (playerTeamDynamicRating); var dynamic = (teamDynamicRating - playerTeam.TeamRating) + playerTeam.Player1Rating; dynamic = Clamp(dynamic, 1, dynamicCap); if (_ratingRule.EnableDynamicRatingCap) { return(playerTeam.Player1Gender.ToLower() == "m" ? Math.Min(16.5f, dynamic) : Math.Min(13.5f, dynamic)); } // cap dynamic return(dynamic); } if (playerId == playerTeam.Player2Id) { var playerTeamDynamicRating = CalculateTeamDynamicRating(CalculateMatchBaseline(playerTeam.Player2Rating, opponentTeam.TeamRating), teamAdjustment); var dynamicCap = (playerTeamDynamicRating); var dynamic = (teamDynamicRating - playerTeam.TeamRating) + playerTeam.Player2Rating; dynamic = Clamp(dynamic, 1, dynamicCap); if (_ratingRule.EnableDynamicRatingCap) { return(playerTeam.Player2Gender.ToLower() == "m" ? Math.Min(16.5f, dynamic) : Math.Min(13.5f, dynamic)); } return(dynamic); } throw new RatingException("Could not map player to team"); }
private static double CalculateOpponentRatingReliabilityFactor(TeamInfo opponentTeam) { // mean of opponent reliabilities return(opponentTeam.TeamReliability); }
public RatingInfoDoubles CalculateDoublesUtr(List <Result> results, Dictionary <int, Player> players, Player player) { var ratingInfo = new RatingInfoDoubles(); double sumWeight = 0; double sumDynamicXWeight = 0; double sumPlayerReliabilityWeight = 0; int resultCount = 0; var eligibleResultsList = new List <int>(); foreach (var result in results) { var winner1 = players[result.Winner1Id]; var winner2 = players[result.Winner2Id ?? 0]; var loser1 = players[result.Loser1Id]; var loser2 = players[result.Loser2Id ?? 0]; if (winner1 == null || winner2 == null || loser1 == null || loser2 == null) { continue; } double w1Reliability; double w2Reliability; double l1Reliability; double l2Reliability; // this value should be pre-calculated var isCompetitiveResult = result.Competitiveness.Equals("Competitive", StringComparison.OrdinalIgnoreCase); winner1.Stats.AssignedRating = AssignPlayerRatingForDoubles(winner1, winner2, loser1, loser2, isCompetitiveResult, out w1Reliability); winner2.Stats.AssignedRating = AssignPlayerRatingForDoubles(winner2, winner1, loser1, loser2, isCompetitiveResult, out w2Reliability); loser1.Stats.AssignedRating = AssignPlayerRatingForDoubles(loser1, loser2, winner1, winner2, isCompetitiveResult, out l1Reliability); loser2.Stats.AssignedRating = AssignPlayerRatingForDoubles(loser2, loser1, winner1, winner2, isCompetitiveResult, out l2Reliability); // skip result if someone cannot be assigned if (winner1.Stats.AssignedRating == null || winner2.Stats.AssignedRating == null || loser1.Stats.AssignedRating == null || loser2.Stats.AssignedRating == null) { continue; } winner1.Stats.AssignedReliability = w1Reliability; winner2.Stats.AssignedReliability = w2Reliability; loser1.Stats.AssignedReliability = l1Reliability; loser2.Stats.AssignedReliability = l2Reliability; TeamInfo playerTeam; TeamInfo opponentTeam; // identify player's team if (player.Id == winner1.Id || player.Id == winner2.Id) { playerTeam = new TeamInfo(winner1, winner2, false); opponentTeam = new TeamInfo(loser1, loser2, true); } else { playerTeam = new TeamInfo(loser1, loser2, false); opponentTeam = new TeamInfo(winner1, winner2, true); } if (opponentTeam.TeamReliability <= 0) { continue; } var dynamic = CalculateDynamicRating(player.Id, playerTeam, opponentTeam, result); dynamic = Math.Truncate(1000 * dynamic) / 1000; var numPartnerResults = GetPartnerResults(playerTeam, results); var matchWeights = CalculateMatchWeight(playerTeam, opponentTeam, result, numPartnerResults); if (matchWeights.MatchWeight >= _ratingRule.eligibleResultsWeightThreshold) { eligibleResultsList.Add(result.Id); } sumDynamicXWeight += (dynamic * matchWeights.MatchWeight); sumWeight += matchWeights.MatchWeight; // divide by interpool coefficient so it doesn't skew player reliability sumPlayerReliabilityWeight += (matchWeights.MatchWeight / matchWeights.InterpoolCoeffecient); resultCount++; } // store active doubles results player.Stats.ActiveDoublesResults = JsonConvert.SerializeObject(eligibleResultsList.ToArray()); var n = resultCount; // if no valid results, don't calculate if (n <= 0) { ratingInfo.Rating = 0; ratingInfo.Reliability = 0; return(ratingInfo); } var doublesUtr = (sumWeight > 0) ? (sumDynamicXWeight / sumWeight) : 1d; var doublesReliability = players[player.Id].Stats.AssignedReliability; var singlesReliabilityAsPercent = ((player.Stats.RatingReliability ?? 0) / 10); var doublesReliabilityAsPercent = (doublesReliability / 10); // Determination of Player’s (Singles) UTR and corresponding Player’s (singles) UTR Reliability. Factor in if available var singlesWeight = player.Stats.RatingReliability >= _ratingRule.singlesWeightReliabilityThreshold ? _ratingRule.singlesWeightOnDoubles : 0; if (player.Stats.FinalRating > 0 && player.Stats.RatingReliability > 0) { ratingInfo.Rating = ((n * (doublesUtr * doublesReliabilityAsPercent)) + (singlesWeight * ((player.Stats.FinalRating ?? 0) * singlesReliabilityAsPercent))) / ((n * doublesReliabilityAsPercent) + (singlesWeight * singlesReliabilityAsPercent)); } else { ratingInfo.Rating = (n * (doublesUtr * doublesReliabilityAsPercent)) / (n * doublesReliabilityAsPercent); } ratingInfo.Reliability = CalculatePlayerReliability(sumPlayerReliabilityWeight); return(ratingInfo); }