diff --git a/App/Program.cs b/App/Program.cs index 24a34c4..939997d 100644 --- a/App/Program.cs +++ b/App/Program.cs @@ -9,7 +9,7 @@ Console.WriteLine("ideal overround: {0}", idealOverround); Console.WriteLine("ideal booksum: {0}", idealOverround * fairBooksum); -IOverroundMethod[] methods = [new Multiplicative(), new Additive(), new Power(), new OddsRatio()]; +IOverroundMethod[] methods = [new Multiplicative(), new Additive(), new Power(), new OddsRatio(), new Shin()]; foreach (var method in methods) { double[] odds = method.Apply(fairPrices, idealOverround); diff --git a/Overrounds.Tests/ShinTest.cs b/Overrounds.Tests/ShinTest.cs new file mode 100644 index 0000000..41d6040 --- /dev/null +++ b/Overrounds.Tests/ShinTest.cs @@ -0,0 +1,48 @@ +namespace Overrounds.Tests; + +[TestClass] +public class ShinTest +{ + private const double Delta = 1e-3; + + private readonly IOverroundMethod method = new Shin(); + + [TestMethod] + public void TestApplyNoOverround() + { + double idealOverround = 1.0; + double[] fairPrices = [1 / 0.1, 1 / 0.2, 1 / 0.3, 1 / 0.4]; + double[] odds = method.Apply(fairPrices, idealOverround); + Assert.AreEqual(idealOverround, Booksum.FromPrices(odds), Delta); + ArrayAssert.AreEqual([1 / 0.1, 1 / 0.2, 1 / 0.3, 1 / 0.4], odds, Delta); + } + + [TestMethod] + public void TestApplyWithFairBooksumOf1() + { + { + double idealOverround = 1.15; + double[] fairPrices = [1 / 0.1, 1 / 0.2, 1 / 0.3, 1 / 0.4]; + double[] odds = method.Apply(fairPrices, idealOverround); + Assert.AreEqual(idealOverround, Booksum.FromPrices(odds), Delta); + ArrayAssert.AreEqual([7.731, 4.253, 2.940, 2.248], odds, Delta); + } + { + double idealOverround = 1.15; + double[] fairPrices = [1 / 0.01, 1 / 0.29, 1 / 0.3, 1 / 0.4]; + double[] odds = method.Apply(fairPrices, idealOverround); + Assert.AreEqual(idealOverround, Booksum.FromPrices(odds), Delta); + ArrayAssert.AreEqual([36.059, 3.012, 2.92, 2.238], odds, Delta); + } + } + + [TestMethod] + public void TestApplyWithFairBooksumOf2() + { + double idealOverround = 1.15; + double[] fairPrices = [1 / 0.2, 1 / 0.4, 1 / 0.6, 1 / 0.8]; + double[] odds = method.Apply(fairPrices, idealOverround); + Assert.AreEqual(idealOverround * 2, Booksum.FromPrices(odds), Delta); + ArrayAssert.AreEqual([3.660, 2.099, 1.479, 1.144], odds, Delta); + } +} \ No newline at end of file diff --git a/Overrounds/Shin.cs b/Overrounds/Shin.cs new file mode 100644 index 0000000..0cf966d --- /dev/null +++ b/Overrounds/Shin.cs @@ -0,0 +1,47 @@ +namespace Overrounds; + +public class Shin : IOverroundMethod +{ + private const double InitStep = 0.01; + private const double ErrorThreshold = 1e-6; + private const int MaxIterations = 100; + + private static double GetInitEstimate(double fairBooksum, double idealBooksum, int numOutcomes) + { + return (idealBooksum - fairBooksum) / (numOutcomes - fairBooksum); + } + + private static double ComputeAdjustment(double z, double prob) + { + return Math.Sqrt(z *prob + (1 - z) * prob * prob); + } + + private static double[] ComputeOdds(double[] fairPrices, double fairBooksum, double z) + { + double[] odds = new double[fairPrices.Length]; + double sigmaAdjustments = 0; + for (int i = 0; i < fairPrices.Length; i++) + { + sigmaAdjustments += ComputeAdjustment(z, 1 / fairPrices[i]); + } + for (int i = 0; i < fairPrices.Length; i++) + { + odds[i] = 1 / (ComputeAdjustment(z, 1 / fairPrices[i]) * sigmaAdjustments / fairBooksum); + } + return odds; + } + + public double[] Apply(double[] fairPrices, double idealOverround) + { + double fairBooksum = Booksum.FromPrices(fairPrices); + double idealBooksum = idealOverround * fairBooksum; + double initEstimate = GetInitEstimate(fairBooksum, idealBooksum, fairPrices.Length); + Solution solution = Solver.Solve(initEstimate, InitStep, ErrorThreshold, MaxIterations, estimate => + { + double[] odds = ComputeOdds(fairPrices, fairBooksum, estimate); + double booksum = Booksum.FromPrices(odds); + return Math.Pow(booksum - idealBooksum, 2); + }); + return ComputeOdds(fairPrices, fairBooksum, solution.Value); + } +} \ No newline at end of file diff --git a/README.md b/README.md index bd19e8c..18d279d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ === [![Build Status](https://img.shields.io/github/actions/workflow/status/kindredgroup/overround-methods/dotnet.yml?branch=master&style=flat-square&logo=github)](https://github.com/kindredgroup/overround-methods/actions/workflows/dotnet.yml) -A collection of overround application methods: _Multiplicative_, _Additive_, _Power_ and _Odds-Ratio_. +A collection of overround application methods: _Multiplicative_, _Additive_, _Power_, _Odds-Ratio_ and _Shin_. # Examples ```csharp @@ -16,7 +16,7 @@ Console.WriteLine("fair booksum: {0}", fairBooksum); double idealOverround = 1.15; Console.WriteLine("ideal overround: {0}", idealOverround); -IOverroundMethod[] methods = [new Multiplicative(), new Additive(), new Power(), new OddsRatio()]; +IOverroundMethod[] methods = [new Multiplicative(), new Additive(), new Power(), new OddsRatio(), new Shin()]; foreach (var method in methods) { double[] odds = method.Apply(fairPrices, idealOverround);