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);
        }
Beispiel #14
0
 public static float DoCoeffcientCalculation(float delta, RatingRule rule)
 {
     return(1 - (rule.CompetitivenessFactorMultiplier * delta));
 }
Beispiel #15
0
        public static bool IsMatchLopsided(Result matchInfo, RatingRule rule)
        {
            float matchRatio = matchInfo.LoserGameCount / ((float)matchInfo.WinnerGameCount + matchInfo.LoserGameCount);

            return(matchRatio <= rule.LopsidedGameRatio);
        }
Beispiel #16
0
        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);
            }
        }
Beispiel #17
0
        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);
        }
Beispiel #18
0
        // 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;
 }