private static float GetPercentCollegeNationality(List <Result> allResults, RatingRule rule) { var importIds = new[] { 6, 8, 14, 16, 36, 37, 24, 7 }; // TODO: hardcoded import types var importSubTypes = new[] { "LTATour", "AustraliaAMT" }; var total = 0; var totalCollege = 0; if (allResults == null) // skip if player has no results { return(0); } allResults = new List <Result>(allResults.OrderByDescending(r => r.ResultDate)); var previousResult = new Result(); foreach (Result result in allResults) { if (result.ResultDate > rule.ResultThreshold) // Last 30 or Last Year (variable based on rating rules) { int compare = DateTime.Compare(result.ResultDate, previousResult.ResultDate); if ((total < rule.NumberOfResults) || (compare == 0)) { total++; if (result.DataImportTypeId != null) { if (importIds.Contains((int)result.DataImportTypeId) || importSubTypes.Contains(result.DataImportSubType)) { totalCollege++; } } } } } return(total <= 0 ? 0 : (float)totalCollege / total); }
private float GetPercentCollegeNationality(List <Result> allResults, RatingRule rule) { var importIds = new[] { 6, 8, 14, 16, 36, 37, 24, 7 }; // TODO: hardcoded import types var importSubTypes = new[] { "LTATour", "AustraliaAMT" }; var total = 0; var totalCollege = 0; if (allResults == null) // skip if player has no results { return(0); } allResults = allResults.OrderByDescending(x => x.ResultDate).ToList(); foreach (var result in allResults) { total++; if (result.DataImportTypeId != null) { if (importIds.Contains((int)result.DataImportTypeId) || importSubTypes.Contains(result.DataImportSubType)) { totalCollege++; } } } return(total <= 0 ? 0 : (float)totalCollege / total); }
public static RatingRule GetDefault(string type, string connStr) { RatingRule r = new RatingRule(); /* * r.ResultThreshold = DateTime.Now.AddMonths(-1 * Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["OldestResultInMonths"])); * r.NumberOfResults = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["NumberOfResults"]); * r.MaxReliability = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["MaxReliability"]); * r.ReliabilityWeight = Convert.ToSingle(System.Configuration.ConfigurationManager.AppSettings["ReliabilityWeight"]); * * * SqlConnection conn = new SqlConnection("Server=.\\SQLSERVER;Database=universaltennis_rating;Trusted_Connection=True;Connection Timeout=90"); * conn.Open(); * var magicNumbers = conn.Query<string>("select top 1 Doc from adminsettings where type = @param", * new { param = "MagicNumbers" }).First(); * conn.Close(); * var magicNumberMap = JsonConvert.DeserializeObject<Dictionary<string, double>>(magicNumbers); * r.MaxLevelDifference = (float)magicNumberMap["MaxLevelDifference"]; * r.MaxLevelDifferenceProfessional = (float)magicNumberMap["MaxLevelDifferenceProfessional"]; */ using (var conn = new SqlConnection(connStr)) { string curveKey; switch (type) { case "singles": r = SetSinglesVariables(r, conn); curveKey = "V2NormalizationCurve"; break; case "doubles": r = SetDoublesVariables(r, conn); curveKey = "DoublesNormalizationCurve"; break; default: r = SetSinglesVariables(r, conn); curveKey = "V2NormalizationCurve"; break; } const string query = @"select Doc from AlgorithmSetting where type = @Type"; var settings = conn.Query <AlgorithmSettings>(query, new { Type = curveKey }).SingleOrDefault(); if (settings == null) { throw new RatingException("Could not find curve settings"); } r.NormalizationCurve = JsonConvert.DeserializeObject <V2NormalizationCurve>(settings.Doc); } return(r); }
/* * Match Relibility coefficient is 1.0 for 3 and 5 set matches, 0.7 for pro set, 0.4 for miniset */ public float CalculateMatchFormatReliability(Result r, RatingRule rule) { if (!r.IsDNF) { switch (r.MatchType) { case Result.MatchFormat.BestOfFiveSets: return(rule.BestOfFiveSetReliability); case Result.MatchFormat.BestOfThreeSets: return(rule.BestOfThreeSetReliability); case Result.MatchFormat.EightGameProSet: return(rule.EightGameProSetReliability); case Result.MatchFormat.MiniSet: return(rule.MiniSetReliability); case Result.MatchFormat.OneSet: return(rule.OneSetReliability); default: throw new ArgumentException("Match type " + r.MatchType + " is not one of the supported MatchFormat", "matchType"); } } else { switch (r.CompletedSets) { case 0: return(0.0f); case 1: return(0.5f); case 2: return(0.8f); case 3: return(0.9f); case 4: return(0.95f); default: throw new ArgumentException("Completed set count: " + r.CompletedSets + " out of range", "completedSets"); } } }
private static RatingRule SetDoublesVariables(RatingRule r, IDbConnection conn) { var settings = conn.Query <AlgorithmSettings>("select Doc from algorithmsetting where type = @Type", new { Type = "DoublesVariables" }).First(); var variables = JsonConvert.DeserializeObject <V2Settings>(settings.Doc); r.MaxUTRDelta = variables.MaxUTRDelta; r.NormalMatchMaxUTRDelta = variables.NormalMatchMaxUTRDelta; r.CloseMatchMaxUTRDelta = variables.CloseMatchMaxUTRDelta; r.BestOfFiveSetReliability = variables.BestOfFiveSetReliability; r.BestOfThreeSetReliability = variables.BestOfThreeSetReliability; r.EightGameProSetReliability = variables.EightGameProSetReliability; r.MiniSetReliability = variables.MiniSetReliability; r.OneSetReliability = variables.OneSetReliability; r.LopsidedMatchReliability = variables.LopsidedMatchReliability; r.LopsidedGameRatio = variables.LopsidedGameRatio; r.UnderDogMatchReliability = variables.UnderDogMatchReliability; r.CompetitiveUnderDogMatchReliability = variables.CompetitiveUnderDogMatchReliability; r.CompetitivenessFactorMultiplier = variables.CompetitivenessFactorMultiplier; r.MinRatingRelibility = variables.MinRatingRelibility; r.BenchmarkMatchCoeffecient = variables.BenchmarkMatchCoeffecient; r.maleUTRScaleGraduationHigh = variables.MaleUTRScaleGraduationHigh; r.maleUTRScaleGraduationLow = variables.MaleUTRScaleGraduationLow; r.interpoolCoefficientCollege = variables.InterpoolCoefficientCollege; r.interpoolCoefficientCountry = variables.InterpoolCoefficientCountry; r.EnableOpponentRatingReliability = variables.EnableOpponentRatingReliability; r.EnableMatchFormatReliability = variables.EnableMatchFormatReliability; r.EnableMatchFrequencyReliability = variables.EnableMatchFrequencyReliability; r.EnableMatchCompetitivenessCoeffecient = variables.EnableMatchCompetitivenessCoeffecient; r.EnableBenchmarkMatchCoeffecient = variables.EnableBenchmarkMatchCoeffecient; r.EnableInterpoolCoeffecient = variables.EnableInterpoolCoeffecient; r.EnableDynamicRatingCap = variables.EnableDynamicRatingCap; r.femaleUTRScaleGraduationHigh = variables.FemaleUTRScaleGraduationHigh; r.femaleUTRScaleGraduationLow = variables.FemaleUTRScaleGraduationLow; r.minPartnerFrequencyFactor = variables.MinPartnerFrequencyFactor; r.partnerSpreadAdjustmentFactor = variables.PartnerSpreadAdjustmentfactor; r.EnablePartnerFrequencyReliability = variables.EnablePartnerFrequencyReliability; r.singlesWeightOnDoubles = variables.SinglesWeightOnDoubles; r.singlesWeightReliabilityThreshold = variables.SinglesWeightReliabilityThreshold; r.provisionalSinglesReliabilityThreshold = variables.ProvisionalSinglesReliabilityThreshold; r.provisionalDoublesReliabilityThreshold = variables.ProvisionalDoublesReliabilityThreshold; r.disconnectedPoolThreshold = variables.DisconnectedPoolThreshold; r.EnableCompetitivenessFilter = variables.EnableCompetitivenessFilter; r.benchmarkTrailSpan = variables.BenchmarkTrailSpan; r.eligibleResultsWeightThreshold = variables.EligibleResultWeightThreshold; return(r); }
public float CalculateInterpoolCoefficient(Player player, Player opponent, RatingRule rule, out bool collegeInterpoolApplied) { var isForeign = (player.CountryId != opponent.CountryId) && player.CountryId > 0 && opponent.CountryId > 0; var playerIsCollege = player.CollegeId > 0; var opponentIsCollege = opponent.CollegeId > 0; if (playerIsCollege && opponentIsCollege) { collegeInterpoolApplied = false; return(1.0f); } if (playerIsCollege ^ opponentIsCollege) // college vs non college { collegeInterpoolApplied = true; return(rule.interpoolCoefficientCollege); } collegeInterpoolApplied = false; return(isForeign ? rule.interpoolCoefficientCountry : 1.0f); }
private void UpdateDisconnectedPools(RatingRule rule, IDictionary <int, List <Result> > results) { _status = "Checking for disconnected pools..."; var graph = SanityCheck.FindGroups(results, rule.disconnectedPoolThreshold); var disconnectedPlayerIds = new List <int>(); foreach (var pool in graph) { disconnectedPlayerIds.AddRange(pool.Nodes); } using (var connection = new SqlConnection(_config.ConnectionStrings.DefaultConnection)) { const string rel = "RatingReliability"; const string rat = "ActualRating"; // break update list into batches of 2000 since mssql has a param cap of 2100 for (var i = 0; i < (float)disconnectedPlayerIds.Count / 2000; i++) { var sub = disconnectedPlayerIds.Skip(i * 2000).Take(2000); connection.Execute( @"Update PlayerRating Set FinalRating = 0, " + rat + " = 0, " + rel + " = 0 WHERE playerId in @Disconnected", new { Disconnected = sub }, commandTimeout: 300); } } }
//Adjustment Factor (same sex) = [(%Games Won * 2 * X) - X] where X = "UTR Scale Graduation" //Adjustment Factor (male vs female) = [((%Games Won * (X + Y)) - ((X + Y)/2)] where X,Y = "UTR Scale Graduation M/F" public double CalculateMatchAdjustmentFactor(Player playerInfo, string opponentGender, double baseline, Result r, RatingRule rule) { // use college scale factors if it's a college match var scaleHighMale = r.IsCollegeMatch ? rule.maleUTRCollegeScaleGraduationHigh : rule.maleUTRScaleGraduationHigh; var scaleLowMale = r.IsCollegeMatch ? rule.maleUTRCollegeScaleGraduationLow : rule.maleUTRScaleGraduationLow; var scaleHighFemale = r.IsCollegeMatch ? rule.femaleUTRCollegeScaleGraduationHigh : rule.femaleUTRScaleGraduationHigh; var scaleLowFemale = r.IsCollegeMatch ? rule.femaleUTRCollegeScaleGraduationLow : rule.femaleUTRScaleGraduationLow; var pctGamesWon = r.PercentOfGamesWon(playerInfo.Id); // if baseline is 1, scale grad low should be used. if 16.5, scale grad high used // sliding linear scale for baselines in between var scaleGraduationMale = scaleLowMale + ((baseline - 1) * (scaleHighMale - scaleLowMale)) / 15.5d; scaleGraduationMale = Clamp(scaleGraduationMale, scaleLowMale, scaleHighMale); var scaleGraduationFemale = scaleLowFemale + ((baseline - 1) * (scaleHighFemale - scaleLowFemale)) / 15.5d; scaleGraduationFemale = Clamp(scaleGraduationFemale, scaleLowFemale, scaleHighFemale); if (playerInfo.Gender.Equals(opponentGender)) { if (opponentGender.ToLower().Equals("m")) { var adjM = (pctGamesWon * 2 * scaleGraduationMale) - scaleGraduationMale; if (r.IsCollegeMatch && r.Loser1Id == playerInfo.Id && baseline < rule.maleScaleLossPercentageMaxLevel) { adjM = adjM * rule.maleUTRCollegeScaleLossPercentage; } return(adjM); } var adjF = (pctGamesWon * 2 * scaleGraduationFemale) - scaleGraduationFemale; if (r.IsCollegeMatch && r.Loser1Id == playerInfo.Id && baseline < rule.femaleScaleLossPercentageMaxLevel) { adjF = adjF * rule.femaleUTRCollegeScaleLossPercentage; } return(adjF); } // Male vs Female - use average var scaleGraduation = (scaleGraduationFemale + scaleGraduationMale) / 2; return((pctGamesWon * 2 * scaleGraduation) - scaleGraduation); }
public RatingInfo.WeightingFactors CalculateMatchResultRelibilityFactor(Player player, Result r, Player opponent, float opponentFrequency, bool againstBenchmark, RatingRule rule) { /* * Match Result Reliabiliy Factor is A x B x C x D * Where: * A = Opponent's Player Rating Reliability * B = Match Format Reliability * C = Match Frequency Reliability * D = Match Competitiveness Coeffecient * E = Benchmark Match Coeffecient * F = Interpool Match Coeffecient */ float a, b, c, d, rr, f; bool collegeInterpoolApplied = false; a = (rule.EnableOpponentRatingReliability) ? CalculateOpponentRatingReliability(opponent) : 10.0f; b = (rule.EnableMatchFormatReliability) ? CalculateMatchFormatReliability(r, rule) : 1.0f; c = (rule.EnableMatchFrequencyReliability) ? 2 / (opponentFrequency + 1) : 1.0f; d = (rule.EnableMatchCompetitivenessCoeffecient) ? MatchCompetivenessCalculator.CalculateMatchCompetivenessCoeffecient(player, opponent, r, rule) : 1.0f; f = (rule.EnableInterpoolCoeffecient) ? CalculateInterpoolCoefficient(player, opponent, rule, out collegeInterpoolApplied) : 1.0f; rr = a * b * c * d * f; float coef = 1.0f; if (againstBenchmark) { coef = (rule.EnableBenchmarkMatchCoeffecient) ? rule.BenchmarkMatchCoeffecient : 1.0f; rr = rr * coef; } RatingInfo.WeightingFactors wf = new RatingInfo.WeightingFactors(); wf.MatchFormatReliability = b; wf.MatchFrequencyReliability = c; wf.MatchCompetitivenessCoeffecient = d; wf.BenchmarkMatchCoeffecient = coef; wf.OpponentRatingReliability = a; wf.InterpoolCoeffecient = f; // TESTING: use college interpool as match weight if it is applied //wf.MatchWeight = collegeInterpoolApplied ? (rule.interpoolCoefficientCollege*10) : (float)Math.Truncate(100000 * rr) / 100000; wf.MatchWeight = (float)Math.Truncate(100000 * rr) / 100000; return(wf); }
public float CalculatePlayerReliability(float sumOfResultReliability, RatingRule rule) { float playerReliability = (rule.ReliabilityWeight + sumOfResultReliability) / rule.ReliabilityWeight; return((playerReliability > rule.MaxReliability) ? rule.MaxReliability : playerReliability); // Max ralibility is limited in the rating rules }
public float CalculateDynamicRating(Player playerInfo, Player opponent, Result r, Dictionary <int, Player> playerRepo, RatingRule rule) { var baseline = CalculateMatchBaseline(r, playerRepo); var af = CalculateMatchAdjustmentFactor(playerInfo, opponent.Gender, baseline, r, rule); if (double.IsNaN(af) || double.IsNaN(baseline)) { System.Diagnostics.Debug.WriteLine(af); } var dynamicRating = (float)baseline + (float)af; if (dynamicRating < 1f) { dynamicRating = 1f; } if (rule.EnableDynamicRatingCap) { dynamicRating = playerInfo.Gender.ToLower() == "m" ? Math.Min(16.5f, dynamicRating) : Math.Min(13.5f, dynamicRating); } return((float)Math.Truncate(1000 * dynamicRating) / 1000); }
public async Task UpdateRating(int count, RatingRule rule, int jobId) { // resolve any events await _playerService.ResolvePlayerEvents(); // await _resultService.ResolveResultEvents(); _ratingJobId = jobId; try { // always update the rule _ratingRule = rule ?? RatingRule.GetDefault("doubles", _config.ConnectionStrings.DefaultConnection); await LogStatus("Loading Players..."); _logger.LogInformation("Retrieving Players..."); await LogStatus("Loading players..."); var resultThreshold = DateTime.UtcNow.AddMonths(-1 * int.Parse(_config.OldestResultInMonths)); var players = (await _playerService.GetPlayersWithResults("doubles", resultThreshold)).ToDictionary(item => item.Id); // all doubles results in the last year _logger.LogInformation("Retrieving Results..."); await LogStatus("Loading results..."); var results = await _resultService.GetPlayerResultsFromYear("doubles", resultThreshold); _logger.LogInformation("Running calculations..."); for (var i = 0; i < count; i++) { await LogStatus($"Running calculations - Iteration {i + 1}"); DoCalc(players, i + 1, results); } _logger.LogInformation("Calculating Competitiveness..."); await LogStatus("Calculating competitiveness..."); foreach (var player in players) { var ratingResults = LoadRatingResults(results[player.Value.Id]); player.Value.Stats.CompetitiveMatchPctDoubles = CalculateCompetitiveness(ratingResults); } _logger.LogInformation("Correcting players..."); await LogStatus("Correcting players..."); NormalizeRatingsII(players.Values.ToList(), results); _logger.LogInformation("Saving Ratings"); await LogStatus("Saving Ratings..."); using (var connection = new SqlConnection(_config.ConnectionStrings.DefaultConnection)) { connection.Open(); using (var transaction = connection.BeginTransaction()) { const string updateQuery = "Update PlayerRating Set FinalDoublesRating = @FinalDoublesRating, DoublesRating = @DoublesRating, DoublesReliability = @DoublesReliability," + " CompetitiveMatchPctDoubles = @CompetitiveMatchPctDoubles, ActiveDoublesResults = @ActiveDoublesResults, DoublesBenchmarkRating = @DoublesBenchmarkRating" + " PlayerGender = @Gender Where playerid = @Id"; foreach (var player in players) { connection.Execute(updateQuery, new { DoublesRating = player.Value.Stats.DoublesRating, DoublesReliability = player.Value.Stats.DoublesReliability, FinalDoublesRating = player.Value.Stats.FinalDoublesRating, CompetitiveMatchPctDoubles = player.Value.Stats.CompetitiveMatchPctDoubles, DoublesBenchmarkRating = player.Value.Stats.DoublesBenchmarkRating, Id = player.Value.Id, Gender = player.Value.Gender, ActiveDoublesResults = player.Value.Stats.ActiveDoublesResults }, transaction: transaction); } transaction.Commit(); } } // clean up players players = null; GC.Collect(); await LogStatus("Checking for disconnected pools..."); UpdateDisconnectedPools(results); await LogStatus("Completed"); _logger.LogInformation("Algorithm Completed Successfully"); } catch (Exception e) { LogException(e); _logger.LogInformation("Algorithm Failed"); await LogStatus("Failed"); } finally { // close the job var job = await _ratingJobRepository.GetById(_ratingJobId); job.EndTime = DateTime.Now; await _ratingJobRepository.Update(job); } }
public RatingInfo DoResultsCalculation(Player playerInfo, Result r, ArrayList results, Dictionary <int, Player> playerRepo, RatingRule rule, Dictionary <int, int> matchFrequency) { RatingInfo ratingInfo = new RatingInfo { SurfaceType = r.SurfaceType, Date = r.ResultDate, IsMastersOrGrandslam = r.IsMastersOrGrandslam }; var opponent = playerRepo[GetOpponentId(playerInfo.Id, r)]; //info about opponent //var opponent = GetOpponent(playerInfo.Id, r, playerRepo); if (opponent.BenchmarkRating != null && opponent.BenchmarkRating > 0) { opponent.Stats.Rating = (double)opponent.BenchmarkRating; } ratingInfo.Rating = CalculateDynamicRating(playerInfo, opponent, r, playerRepo, rule); if (float.IsNaN(ratingInfo.Rating)) { System.Diagnostics.Debug.WriteLine(ratingInfo.Rating); } ratingInfo.AgainstBenchmark = (opponent.Stats.IsBenchmark) ? true : false; float opponentFrequency = 0; try { opponentFrequency = (float)matchFrequency[opponent.Id]; // value from dictionary } catch (Exception e) { _logger.LogWarning(e, opponent.Id.ToString()); } RatingInfo.WeightingFactors wf = CalculateMatchResultRelibilityFactor(playerInfo, r, opponent, opponentFrequency, ratingInfo.AgainstBenchmark, rule); ratingInfo.Reliability = wf.MatchWeight; //reliability for match ratingInfo.weightingFactors = wf; return(ratingInfo); }
public static float DoCoeffcientCalculation(float delta, RatingRule rule) { return(1 - (rule.CompetitivenessFactorMultiplier * delta)); }
public static bool IsMatchLopsided(Result matchInfo, RatingRule rule) { float matchRatio = matchInfo.LoserGameCount / ((float)matchInfo.WinnerGameCount + matchInfo.LoserGameCount); return(matchRatio <= rule.LopsidedGameRatio); }
public static float CalculateUnderdogMatch(Player player, Player opponent, Result matchInfo, RatingRule rule) { Player winner, loser; if (matchInfo.Winner1Id == player.Id) { winner = player; loser = opponent; } else { winner = opponent; loser = player; } if (winner.Stats.Rating > loser.Stats.Rating && !CompetiveThresholdReached(matchInfo)) //If the expected player won and the loser didn't reach competitive threshold, it's expected. { return(rule.UnderDogMatchReliability); } else //Underdog was competitive { return(rule.CompetitiveUnderDogMatchReliability); } }
public static float CalculateMatchCompetivenessCoeffecient(Player player, Player opponent, Result matchInfo, RatingRule rule) { float UTRDelta = (float)Math.Abs(player.Stats.Rating - opponent.Stats.Rating), 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(player, opponent, 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 + ", " + matchInfo.ScoreHtml + ", " + UTRDelta); //coeffecient = 0.0f; //Fail safe, every match should be caught above, may want to throw exception here } return(coeffecient); }
// 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); } }
public async Task UpdateRating(int count, bool asAlternate, int jobId) { // resolve any events await _playerService.ResolvePlayerEvents(); // await _resultService.ResolveResultEvents(); _ratingJobId = jobId; var conn = new SqlConnection(_config.ConnectionStrings.DefaultConnection); SqlTransaction transaction = null; RatingRule rule = RatingRule.GetDefault("singles", _config.ConnectionStrings.DefaultConnection); try { _logger.LogInformation("Started Singles Algorithm - {0} iterations", count); var resultThreshold = DateTime.UtcNow.AddMonths(-1 * int.Parse(_config.OldestResultInMonths)); _logger.LogInformation("Retrieving Players..."); await LogStatus("Loading players..."); var players = (await _playerService.GetPlayersWithResults("singles", resultThreshold)).ToDictionary(item => item.Id); _logger.LogInformation("Retrieving Results..."); await LogStatus("Loading results..."); var playerResults = await _resultService.GetPlayerResultsFromYear("singles", resultThreshold); _logger.LogInformation("Running calculations..."); await DoCalculation(count, players, rule, playerResults); _logger.LogInformation("Calculating Competitiveness..."); await LogStatus("Calculating competitiveness..."); foreach (var row in players.Values) { if (playerResults == null || playerResults.Count <= 0) { continue; } var player = row; var results = GetRatingResults(player, players, playerResults[player.Id], rule); var comp = CalculateCompetitiveness(results); row.Stats.CompetitiveMatchPct = comp.Item1; row.Stats.RoutineMatchPct = comp.Item2; row.Stats.DecisiveMatchPct = comp.Item3; } _logger.LogInformation("Correcting players..."); await LogStatus("Correcting players..."); DoPostProcessing(playerResults, players.Values.ToList(), rule); _logger.LogInformation("Saving Sub Ratings"); await LogStatus("Saving Sub Ratings..."); foreach (var player in players.Values) { // update sub rating if (player.Stats.SubRating != null) { await _subRatingRepository.AddOrUpdateSubRating(player.Stats.SubRating); } } _logger.LogInformation("Saving Ratings"); await LogStatus("Saving Ratings..."); conn.Open(); transaction = conn.BeginTransaction(); foreach (var player in players.Values) { conn.Execute(@"update playerrating set finalrating = @FinalRating, actualrating = @ActualRating, ratingreliability = @RatingReliability, " + "competitiveMatchPct = @CompetitiveMatchPct, routineMatchPct = @RoutineMatchPct, decisiveMatchPct = @DecisiveMatchPct, " + "inactiveRating = NULL, activeSinglesResults = @ActiveSinglesResults, playergender = @Gender where playerId = @Id", new { Id = player.Id, CompetitiveMatchPct = player.Stats.CompetitiveMatchPct, RoutineMatchPct = player.Stats.RoutineMatchPct, DecisiveMatchPct = player.Stats.DecisiveMatchPct, RatingReliability = player.Stats.RatingReliability, ActualRating = player.Stats.ActualRating, FinalRating = player.Stats.FinalRating, Gender = player.Gender, ActiveSinglesResults = player.Stats.ActiveSinglesResults }, transaction: transaction); } transaction.Commit(); // clean up the players players = null; GC.Collect(); UpdateDisconnectedPools(rule, playerResults); await LogStatus("Completed"); _logger.LogInformation("Algorithm Completed Successfully"); } catch (Exception e) { try { transaction?.Rollback(); } catch (Exception e2) { LogException(e2); } LogException(e); _logger.LogError("Algorithm Failed"); await LogStatus("Failed"); } finally { transaction?.Dispose(); conn.Close(); // close the job var job = await _ratingJobRepository.GetById(_ratingJobId); job.EndTime = DateTime.Now; await _ratingJobRepository.Update(job); } }
/* * Do the rating calculation for each player the number of times specified */ public async Task <Dictionary <int, Player> > DoCalculation(int count, Dictionary <int, Player> players, RatingRule rule, Dictionary <int, List <Result> > playerResults) { for (int i = 0; i < count; i++) { await LogStatus($"Running calculations - Iteration {i + 1}"); foreach (var player in players.Values) { // update progress counters int playerId = player.Id; //Get Player ID ArrayList results = GetRatingResults(player, players, playerResults[playerId], rule); //Get the results of the player's matches RatingInfo rating = new RatingInfo(); if (results != null && results.Count > 0) // skip player if they have no results { rating = CalculatePlayerUTR(player, results, players, rule); } else //No recent result case, this needs to be improved to place players in an inactive & currated state { rating.Rating = (float)player.Stats.Rating; rating.Reliability = 0; } player.Stats.ActualRating = Math.Truncate(rating.Rating * 100) / 100; // store actual value, even for benchmarks player.Stats.RatingReliability = rating.Reliability; player.Stats.SubRating = rating.SubRating; player.Stats.Rating = rating.Rating; player.Stats.Reliability = rating.Reliability; } } return(players); }
public void DoPostProcessing(Dictionary <int, List <Result> > playerResults, List <Player> players, RatingRule rule) { var curve = rule.NormalizationCurve; foreach (var p in players) { var percentAgainstCollege = GetPercentCollegeNationality(playerResults[p.Id], rule); double rating; double reliability; rating = p.Stats.ActualRating ?? 0; reliability = p.Stats.RatingReliability ?? 0; var highLevel = Math.Ceiling((decimal)(rating)); var lowLevel = Math.Floor((decimal)(rating)); float collegeAdjustment; float nationalAdjustment; if (p.Gender.Equals("m", StringComparison.OrdinalIgnoreCase)) { // to correct for the 16 - 16.5 range case var adj = lowLevel >= 16 ? 15.75f : Convert.ToInt32(lowLevel); var fw = lowLevel == 0 ? 0 : curve.CollegeCorrectionsMale[lowLevel.ToString()]; var cw = highLevel == 0 ? 0 : curve.CollegeCorrectionsMale[highLevel.ToString()]; collegeAdjustment = fw + (((float)(rating) - adj) * (cw - fw)); var fw2 = lowLevel == 0 ? 0 : curve.NonCollegeCorrectionsMale[lowLevel.ToString()]; var cw2 = highLevel == 0 ? 0 : curve.NonCollegeCorrectionsMale[highLevel.ToString()]; nationalAdjustment = fw2 + (((float)(rating) - adj) * (cw2 - fw2)); } else { var adj = lowLevel >= 13 ? 12.75f : Convert.ToInt32(lowLevel); var fw = lowLevel == 0 ? 0 : curve.CollegeCorrectionsFemale[lowLevel.ToString()]; var cw = highLevel == 0 ? 0 : curve.CollegeCorrectionsFemale[highLevel.ToString()]; collegeAdjustment = fw + (((float)(rating) - adj) * (cw - fw)); var fw2 = lowLevel == 0 ? 0 : curve.NonCollegeCorrectionsFemale[lowLevel.ToString()]; var cw2 = highLevel == 0 ? 0 : curve.NonCollegeCorrectionsFemale[highLevel.ToString()]; nationalAdjustment = fw2 + (((float)(rating) - adj) * (cw2 - fw2)); } var correction = collegeAdjustment + ((1 - percentAgainstCollege) * (nationalAdjustment - collegeAdjustment)); var finalrating = SmoothRating(p.Gender, reliability, Convert.ToDouble(rating + correction)); p.Stats.FinalRating = Math.Truncate(finalrating * 100) / 100; // correct sub ratings if (p.Stats.SubRating != null) { // assuming all double props are ratings foreach (PropertyInfo prop in p.Stats.SubRating.GetType().GetProperties()) { var type = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; if (type == typeof(double)) { var propValue = prop.GetValue(p.Stats.SubRating, null); if (propValue != null) { prop.SetValue(p.Stats.SubRating, SmoothRating(p.Gender, reliability, (double)propValue + correction)); } } } } } }
/* * Get the matches for the player that are going to be used for calculating UTR */ public ArrayList GetRatingResults(Player playerInfo, Dictionary <int, Player> players, List <Result> allResults, RatingRule rule) { if (allResults == null) // skip if player has no results { return(null); } allResults = new List <Result>(allResults.OrderByDescending(r => r.ResultDate)); ArrayList results = new ArrayList(); Result previousResult = new Result(); var maxResults = rule.NumberOfResults; foreach (Result result in allResults) { if (result.ResultDate > rule.ResultThreshold) // Last 30 or Last Year (variable based on rating rules) { int compare = DateTime.Compare(result.ResultDate, previousResult.ResultDate); if ((results.Count < maxResults) || (compare == 0)) { var opponentId = GetOpponentId(playerInfo.Id, result); var opponentInfo = players[opponentId]; if (opponentInfo.Stats.Reliability > 0 && result.ScoreIsValid()) { results.Add(result); previousResult = result; } // include extra results for each lopsided match if (Math.Abs(playerInfo.Stats.Rating - opponentInfo.Stats.Rating) > 2.5) { maxResults += 1; } } } } return(results); }
public RatingInfo CalculatePlayerUTR(Player player, ArrayList results, Dictionary <int, Player> playerRepo, RatingRule rule) { RatingInfo playerUTR = new RatingInfo(); // store player's UTR and reliability ArrayList resultRatingInfoList = new ArrayList(); // stores the RatingInfo for each match var activeResultsIds = new List <int>(); float SumRatingXReliability = 0; float SumReliabilityRating = 0; float SumReliabilityPlayerReliability = 0; //stores unique opponent IDs and number of times played Dictionary <int, int> opponentsMatchFrequency = CalculateMatchFrequencyReliability(player.Id, results); foreach (Result r in results) { // Calculate Dynamcic Rating and Match Result Reliability Factor for each match, store in list var ri = DoResultsCalculation(player, r, results, playerRepo, rule, opponentsMatchFrequency); resultRatingInfoList.Add(ri); if (ri.weightingFactors.MatchWeight >= rule.eligibleResultsWeightThreshold) { activeResultsIds.Add(r.Id); } } // Store active results used in calculation player.Stats.ActiveSinglesResults = JsonConvert.SerializeObject(activeResultsIds.ToArray()); foreach (RatingInfo ri in resultRatingInfoList) { SumRatingXReliability += ri.Rating * ri.Reliability; // For each match, Dynmic Ratch x Match Reliability SumReliabilityRating += ri.Reliability; // Sum of Match Reliabilities // divide by interpool coefficient so it doesn't skew player reliability SumReliabilityPlayerReliability += ri.Reliability / ri.weightingFactors.InterpoolCoeffecient; } float rating = (SumReliabilityRating == 0.0) ? 0 : (SumRatingXReliability / SumReliabilityRating); playerUTR.Rating = (float)SmoothRating(rating); // Player rating is these number divided playerUTR.Rating = (float)Math.Truncate(100 * playerUTR.Rating) / 100; playerUTR.Reliability = CalculatePlayerReliability(SumReliabilityPlayerReliability, rule); if (float.IsNaN(playerUTR.Rating)) { System.Diagnostics.Debug.WriteLine(playerUTR.Rating); } if (player.IsTop700()) { CalculatePlayerSubUTRs(resultRatingInfoList, playerUTR, player.Stats.Id); } return(playerUTR); }
public void SetRule(RatingRule rule) { _ratingRule = rule; }