oddsmaker 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1376d7ace6063ba0f7eaf60c8dc1b35d76bb505d
4
+ data.tar.gz: e01409a3acd1fdb92da2cce2223b35b6987832e3
5
+ SHA512:
6
+ metadata.gz: d9184cc277ac8eaeb9d9a4fb1475c2c9a37341531ffd995f84e2ebf47d763bb7e736c15881379fefb9a69ec3473eb9ad76d75e293f436654ba45e2bd9fca985a
7
+ data.tar.gz: 92c6699135010fd0734d7edbbfa229c76af9b159c6a785121313fa836a037dc978b8ac7cbc05aaf1ad3d17b248cf498a5729e2db68bf5af6a45b2a49a70ea837
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,8 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.4
5
+ - 2.4.1
6
+ - ruby-head
7
+ - jruby-head
8
+ before_install: gem install bundler -v 1.15.3
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in oddsmaker.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Paul Hoffer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,99 @@
1
+ # Oddsmaker
2
+
3
+ Oddsmaker is to represent and manipulate betting odds. Multiple odds can be represented together, as a `Market`. A `Market` is able to calculate total probability and vig, and can also calculate true probabilities (total of 100%) and calculate overrounded odds (greater than 100%).
4
+
5
+ Oddsmaker can handle American odds, decimal odds, fractional odds, and implied probability, and allows for easy conversion between all of those types.
6
+
7
+ [SportsBookReview](https://www.sportsbookreview.com/picks/tools/odds-converter/) has an excellent page which explains the different types of odds and converts between them.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'oddsmaker'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install oddsmaker
24
+
25
+ ## Usage
26
+
27
+ Let's start with a simple odd:
28
+
29
+ ```ruby
30
+ # create an Odd
31
+ odd = Oddsmaker::Odd.american(300) # => <Oddsmaker::Odd::American @id=300, @value=300>
32
+ odd = Oddsmaker::Odd.american('+300', 'underdog') # optional 2nd param allows for identifying the odd
33
+ odd # => <Oddsmaker::Odd::American @id="underdog", @value=300>
34
+ odd.name # => "underdog"
35
+ odd.value # => 300
36
+ odd.to_s # => "+300"
37
+
38
+ # can convert between formats
39
+ odd.decimal # => <Oddsmaker::Odd::Decimal @id="underdog, @value=4.0>
40
+ odd.fractional # => <Oddsmaker::Odd::Fractional @id="underdog, @value=(3/1)>
41
+ odd.implied_probability # => <Oddsmaker::Odd::ImpliedProbability @id="underdog", @value=0.25>
42
+
43
+ # easy creation any type of odd
44
+ # any odd type can be compared as well
45
+ Oddsmaker::Odd.american(300) == Oddsmaker::Odd.decimal(4)
46
+ Oddsmaker::Odd.decimal(4) == Oddsmaker::Odd.fractional(3/1)
47
+ Oddsmaker::Odd.fractional(3/1) == Oddsmaker::Odd.implied(25)
48
+ Oddsmaker::Odd.implied(25) == Oddsmaker::Odd.american(300)
49
+
50
+ Oddsmaker::Odd.american(300, 'underdog').to_json # => {"name":"underdog","american":300,"decimal":4.0,"fractional":"3/1","implied":0.25}
51
+ ```
52
+
53
+ A collection of `Odd`s can be represented as a `Market`:
54
+
55
+ ```ruby
56
+ # Create a Market with odds as the arguments
57
+ market = Oddsmaker::Market.new(Oddsmaker::Odd.american(300), Oddsmaker::Odd.american(-400))
58
+ market.total_probability # => 1.05
59
+ market.vig # => 0.04761904761904767 (equivalent to (1 - total_probability**-1))
60
+
61
+ # get the odds without vig
62
+ no_vig = market.without_vig # => new market with the vig/overround removed
63
+ no_vig.odds # => [#<Oddsmaker::Odd::American @id=300, @value=320>, #<Oddsmaker::Odd::American @id=-400, @value=-320>]
64
+ no_vig.total_probability # => 1.0
65
+ no_vig.vig # => 0
66
+
67
+ # apply an overround to have vigged odds
68
+ vigged = no_vig.overround!(5) # => 5% overround, returns a new Market
69
+ vigged.odds # => [#<Oddsmaker::Odd::American @id=300, @value=300>, #<Oddsmaker::Odd::American @id=-400, @value=-400>]
70
+ vigged.total_probability # => 1.05
71
+ vigged.vig # => 0.04761904761904767
72
+
73
+ vigged == market # => true # equality comparison only cares about the odds' values. Name and ordering is not relevant
74
+ ```
75
+
76
+ There is also functionality for calculating wagers:
77
+
78
+ ```ruby
79
+ odd = Oddsmaker::Odd.american(300) # => <Oddsmaker::Odd::American @id=300, @value=300>
80
+ wager = odd.wager(100) # => <Oddsmaker::Wager @amount=100, @odd=#<Oddsmaker::Odd::American @id=300, @value=300>>
81
+ wager.amount # => 100
82
+ wager.profit # => 300
83
+ wager.return # => 400
84
+
85
+ # odds are able to calculate their own profit for a wager amount
86
+ odd.profit(100) # => 300
87
+ ```
88
+
89
+ ## Development
90
+
91
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
92
+
93
+ ## Contributing
94
+
95
+ Bug reports and pull requests are welcome on GitHub at https://github.com/phoffer/oddsmaker.
96
+
97
+ ## License
98
+
99
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "oddsmaker"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "pry"
10
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,17 @@
1
+ require 'bigdecimal'
2
+ require "oddsmaker/version"
3
+ require "oddsmaker/market"
4
+ require "oddsmaker/odd"
5
+ require "oddsmaker/wager"
6
+ require "oddsmaker/odd/base"
7
+ require "oddsmaker/odd/implied_probability"
8
+ require "oddsmaker/odd/american"
9
+ require "oddsmaker/odd/decimal"
10
+ require "oddsmaker/odd/fractional"
11
+
12
+ # Oddsmaker is to assist with the calculations involved with betting, especially sports betting.
13
+ # Both a single odd and a set of odds can be represented and manipulated.
14
+ # Additionally, wagers can be represented, allowing for flexibility in calculating profit/return.
15
+ module Oddsmaker
16
+
17
+ end
@@ -0,0 +1,115 @@
1
+ module Oddsmaker
2
+ # Market is a container of multiple odds. This is useful to represent the full data of sports betting.
3
+ # A generic moneyline of +300/-400 can be represented by a market, containing both of the individual odds.
4
+ # This also allows for calculating the total probability, which will be over 100% if set by a sportsbook.
5
+ # Additional functions include the ability to determine the odds without vig (the book's profit),
6
+ # and the ability to calculate odds for a given overround (the total probability above 100%).
7
+ class Market
8
+ attr_reader :odds, :vigged, :name
9
+
10
+ def initialize(*odds, name: nil)
11
+ @name = name
12
+ @odds = odds.flatten
13
+ end
14
+
15
+ # Retrieve an odd by identifier.
16
+ #
17
+ # @param id [String, Integer] Odd identifier. Defaults to provided value.
18
+ # @return [Odd]
19
+ def odd(id)
20
+ odds_hash[id]
21
+ end
22
+
23
+ # Total probability for a set of odds.
24
+ # This will be over 1 if set by a sportsbook.
25
+ #
26
+ # @return [Float] Total probability, with 1.0 representing 100%
27
+ def total_probability
28
+ @total_probability ||= @odds.inject(0) { |total, odd| total + odd.implied_probability.value } # Change to #sum when 2.3 support can be dropped
29
+ end
30
+
31
+ # Calculate equivalent market without any overround (vig).
32
+ #
33
+ # @return [Market] New market with total probability == 1.0
34
+ def without_vig
35
+ self.class.new(without_vig_odds)
36
+ end
37
+
38
+ # Calculate the odds without any overround (vig).
39
+ #
40
+ # @return [<Odd>] Array of odds with the vig removed
41
+ def without_vig_odds
42
+ @without_vig_odds = if total_probability != 1.0
43
+ @odds.map { |odd| odd.without_vig(total_probability) }
44
+ else
45
+ @odds
46
+ end
47
+ end
48
+
49
+ # Total vig (maximum vig under balanced book scenario)
50
+ #
51
+ # @return [Float]
52
+ def vig
53
+ 1 - total_probability**-1
54
+ end
55
+
56
+ # Create market with an added overround amount
57
+ #
58
+ # @param v [Integer] Overround percent
59
+ # @return [Market] New market with overrounded odds
60
+ def overround!(v = 5)
61
+ @vigged = self.class.new(overround_odds!(v))
62
+ end
63
+
64
+ # Calculate the odds with given overround
65
+ #
66
+ # @param v [Integer] Overround percent
67
+ # @return [Array<Odd>] Overrounded odds
68
+ def overround_odds!(v = 5)
69
+ without_vig_odds.map { |odd| odd.overround!(v) }
70
+ end
71
+
72
+ # Compare equality to another market
73
+ #
74
+ # @param other [Market]
75
+ # @return [Boolean]
76
+ def ==(other)
77
+ odds.sort == other.odds.sort
78
+ end
79
+
80
+ # Hash representation of a market.
81
+ # @return [Hash]
82
+ def to_h
83
+ full_odds = if self.total_probability != 1
84
+ no_vig = self.without_vig_odds
85
+ odds.map.with_index { |odd, index| odd.to_h.merge(actual: no_vig[index].implied_probability.value, without_vig: no_vig[index].value) }
86
+ else
87
+ odds.map(&:to_h)
88
+ end
89
+ {
90
+ name: self.name,
91
+ total_probability: self.total_probability,
92
+ vig: self.vig,
93
+ odds: full_odds,
94
+ }
95
+ end
96
+
97
+ # JSON representation of a market.
98
+ # @return [String]
99
+ def to_json
100
+ to_h.to_json
101
+ end
102
+
103
+ private
104
+
105
+ # Hash of odds by name or identifier
106
+ #
107
+ # @return [Hash{String=>Odd}]
108
+ def odds_hash
109
+ @odds_hash ||= @odds.map { |o| [o.id, o] }.to_h.tap do |hash|
110
+ raise 'Must use unique odds identifiers' if hash.size != @odds.size # TODO create custom error classes
111
+ end
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,60 @@
1
+ module Oddsmaker
2
+ # Odds represent a single value that can be bet on.
3
+ # This can be represented and converted between the various forms it can take:
4
+ #
5
+ # - Implied probability
6
+ # - Decimal odds
7
+ # - Fractional odds
8
+ # - American odds
9
+ #
10
+ module Odd
11
+
12
+ # Create an implied probability with the given value.
13
+ #
14
+ # @param value [Float, Integer] Probability value
15
+ # @param id [String] Identifier for this odd. Useful when multiple odds are used in a market
16
+ # @return [ImpliedProbability]
17
+ def self.implied(value, id = nil)
18
+ ImpliedProbability.new(value, id)
19
+ end
20
+
21
+ # Create American odds with the given value.
22
+ #
23
+ # @param value [String, Integer] Odds value
24
+ # @param id [String] Identifier for this odd. Useful when multiple odds are used in a market
25
+ # @return [American]
26
+ def self.american(value, id = nil)
27
+ American.new(value, id)
28
+ end
29
+
30
+ # Create decimal odds with the given value.
31
+ #
32
+ # @param value [Float, Integer] Odds value
33
+ # @param id [String] Identifier for this odd. Useful when multiple odds are used in a market
34
+ # @return [Decimal]
35
+ def self.decimal(value, id = nil)
36
+ Decimal.new(value, id)
37
+ end
38
+
39
+ # Create fractional odds with the given value.
40
+ #
41
+ # @param value [String, Integer] Odds value
42
+ # @param id [String] Identifier for this odd. Useful when multiple odds are used in a market
43
+ # @return [Fractional]
44
+ def self.fractional(value, id = nil)
45
+ Fractional.new(value, id)
46
+ end
47
+
48
+ # Shortcut to creating a new odd based off hash params (i.e. request params).
49
+ # Requires one of `%w[american decimal fraction implied]` keys.
50
+ # Also accepts param `'name'`.
51
+ #
52
+ # @return [Odd]
53
+ def self.new(params = {})
54
+ if (type = %w[american decimal fraction implied].detect { |type| params.key?(type) })
55
+ send(type, params[type], params['name'])
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,83 @@
1
+ module Oddsmaker
2
+ module Odd
3
+ # American Odds class, handling both positive and negative odds.
4
+ # A negative value expresses the dollar amount that would need to be wagered in order to win $100.
5
+ # A positive number expresses the dollar amount that would be won from a $100 wager, excluding the original wager.
6
+ class American < Base
7
+
8
+ def initialize(value, id = nil)
9
+ @id = id || value
10
+ @value = value.to_i
11
+ end
12
+
13
+ # Properly display both positive and negative odds.
14
+ #
15
+ # @return [String]
16
+ def to_s
17
+ @value.positive? ? "+#{@value}" : @value.to_s
18
+ end
19
+
20
+ # Returns self. This creates a consistent API for all odds.
21
+ #
22
+ # @return [self]
23
+ def american
24
+ self
25
+ end
26
+
27
+ # Convert to decimal odds, returning a new object.
28
+ #
29
+ # @return [Decimal]
30
+ def decimal
31
+ @decimal ||= implied_probability.decimal
32
+ end
33
+
34
+ # Convert to fractional odds, returning a new object.
35
+ #
36
+ # @return [Fractional]
37
+ def fractional
38
+ @fractional ||= Fractional.new(calculate_fractional, id)
39
+ end
40
+
41
+ private
42
+
43
+ # Allows for implied probability to create a vigged/unvigged odd and convert it back to source format.
44
+ def type
45
+ :american
46
+ end
47
+
48
+ # Calculate implied probability of an odd.
49
+ #
50
+ # @return [Float]
51
+ def calculate_probability
52
+ calculate_probability ||= if @value.positive?
53
+ 100.0 / (@value + 100)
54
+ else
55
+ -@value.to_f / (100 - @value)
56
+ end
57
+ end
58
+
59
+ # Calculate fractional representation of an odd.
60
+ #
61
+ # @return [Float]
62
+ def calculate_fractional
63
+ calculate_fractional ||= if @value.positive?
64
+ @value / 100.rationalize
65
+ else
66
+ -100 / @value.rationalize
67
+ end
68
+ end
69
+
70
+ # Calculate profit for a wager.
71
+ #
72
+ # @return [Float, Integer]
73
+ def calculate_profit(amount)
74
+ if @value.positive?
75
+ (amount/100.rationalize) * @value
76
+ else
77
+ -100/@value.rationalize * amount
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,100 @@
1
+ module Oddsmaker
2
+ module Odd
3
+ # @abstract Base class to implement common functionality to various types of odds.
4
+ class Base
5
+ include Comparable
6
+ attr_reader :id, :value
7
+ alias :name :id # name is more common scenario, but anything works as an identifier
8
+
9
+ # Display odds as a string
10
+ #
11
+ # @return [String]
12
+ def to_s
13
+ @value.to_s
14
+ end
15
+
16
+ # Calculates a new odd, removing the vig.
17
+ #
18
+ # @return [Odd] Returns the same class as the original object
19
+ def without_vig(total_probability)
20
+ @without_vig = implied_probability.without_vig(total_probability).send(type)
21
+ end
22
+
23
+ # Calculates a new odd, with the given overround percentage.
24
+ #
25
+ # @param v [Integer] Overround percentage (as whole number)
26
+ # @return [Odd] Returns the same class as the original object
27
+ def overround!(v)
28
+ implied_probability.overround!(v).send(type)
29
+ end
30
+
31
+ # Calculate implied probability.
32
+ #
33
+ # @return [ImpliedProbability]
34
+ def implied_probability
35
+ @implied_probability ||= ImpliedProbability.new(calculate_probability, id)
36
+ end
37
+
38
+ # Multiplier is commonly used for parlay and other calculations.
39
+ # It's just decimal odds, but we define it to match common terminology.
40
+ #
41
+ # @return [Float]
42
+ def multiplier
43
+ self.decimal.value
44
+ end
45
+
46
+ # Check two odds for equality.
47
+ #
48
+ # @param other [Odd]
49
+ # @return [Boolean]
50
+ def ==(other)
51
+ implied_probability == other.implied_probability
52
+ end
53
+
54
+ # Compare two odds against each other.
55
+ #
56
+ # @param other [Odd]
57
+ # @return [-1,0,1]
58
+ def <=>(other)
59
+ implied_probability <=> other.implied_probability
60
+ end
61
+
62
+ # Make a wager with this odd.
63
+ #
64
+ # @param amount [Integer, Float] Amount wagered. Can be integer or float.
65
+ # @return [Wager]
66
+ def wager(amount)
67
+ Wager.new(amount, self)
68
+ end
69
+
70
+ # Calculate profit for an amount wagered.
71
+ # Convenient if a `Wager` object is unnecessary.
72
+ #
73
+ # @param amount [Integer, Float] Amount wagered. Can be integer or float.
74
+ # @return [Float, Integer, Rational]
75
+ def profit(amount)
76
+ calculate_profit(amount)
77
+ end
78
+
79
+ # Represent odd as a hash.
80
+ # This will include all forms of the odd.
81
+ # @return [Hash]
82
+ def to_h
83
+ {
84
+ name: self.name,
85
+ american: self.american.value,
86
+ decimal: self.decimal.value,
87
+ fractional: self.fractional.value,
88
+ implied: self.implied_probability.value,
89
+ }
90
+ end
91
+
92
+ # JSON representation of the odd.
93
+ # @return [String]
94
+ def to_json
95
+ to_h.to_json
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,56 @@
1
+ module Oddsmaker
2
+ module Odd
3
+ # Decimal odds express the amount that would be returned from a $1 wager, including the original wager amount.
4
+ # Decimal odds will always be greater than 1.0.
5
+ class Decimal < Base
6
+
7
+ def initialize(value, id = nil)
8
+ @id = id || value
9
+ @value = value.to_f
10
+ end
11
+
12
+ # Returns self. This creates a consistent API for all odds.
13
+ #
14
+ # @return [self]
15
+ def decimal
16
+ self
17
+ end
18
+
19
+ # Convert to American odds, returning a new object.
20
+ #
21
+ # @return [American]
22
+ def american
23
+ @american ||= implied_probability.american
24
+ end
25
+
26
+ # Convert to fractional odds, returning a new object.
27
+ #
28
+ # @return [Fractional]
29
+ def fractional
30
+ @fractional ||= implied_probability.fractional
31
+ end
32
+
33
+ private
34
+
35
+ # Allows for implied probability to create a vigged/unvigged odd and convert it back to source format.
36
+ def type
37
+ :decimal
38
+ end
39
+
40
+ # Calculate implied probability of an odd.
41
+ #
42
+ # @return [Float]
43
+ def calculate_probability
44
+ 1.0 / @value
45
+ end
46
+
47
+ # Calculate profit for a wager.
48
+ #
49
+ # @return [Float, Integer]
50
+ def calculate_profit(amount)
51
+ (@value - 1) * amount
52
+ end
53
+
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,55 @@
1
+ module Oddsmaker
2
+ module Odd
3
+ # Fractional odds express the fraction of a dollar that would be won from a $1 bet.
4
+ class Fractional < Base
5
+
6
+ def initialize(value, id = nil)
7
+ @id = id || value
8
+ @value = value.is_a?(String) ? value.to_r : value.rationalize
9
+ end
10
+
11
+ # Returns self. This creates a consistent API for all odds.
12
+ #
13
+ # @return [self]
14
+ def fractional
15
+ self
16
+ end
17
+
18
+ # Convert to American odds, returning a new object.
19
+ #
20
+ # @return [American]
21
+ def american
22
+ @american ||= implied_probability.american
23
+ end
24
+
25
+ # Convert to decimal odds, returning a new object.
26
+ #
27
+ # @return [Decimal]
28
+ def decimal
29
+ @decimal ||= implied_probability.decimal
30
+ end
31
+
32
+ private
33
+
34
+ # Allows for implied probability to create a vigged/unvigged odd and convert it back to source format.
35
+ def type
36
+ :fractional
37
+ end
38
+
39
+ # Calculate implied probability of an odd.
40
+ #
41
+ # @return [Float]
42
+ def calculate_probability
43
+ @value.denominator.fdiv(@value.denominator + @value.numerator)
44
+ end
45
+
46
+ # Calculate profit for a wager.
47
+ #
48
+ # @return [Float, Integer]
49
+ def calculate_profit(amount)
50
+ @value * amount
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,125 @@
1
+ module Oddsmaker
2
+ module Odd
3
+ # Implied probability is the probability of an event, based on the odds for that event.
4
+ # Implied probability is what translates between various types of odds.
5
+ class ImpliedProbability < Base
6
+
7
+ def initialize(value, id = nil)
8
+ @id = id || value
9
+ @value = value >= 1 ? value.fdiv(100) : value
10
+ end
11
+
12
+ # Calculates a new odd, removing the vig.
13
+ #
14
+ # @return [ImpliedProbability]
15
+ def without_vig(total_probability)
16
+ self.class.new(@value / total_probability, id)
17
+ end
18
+
19
+ # Calculates a new odd, with the given overround percentage.
20
+ #
21
+ # @param v [Integer] Overround percentage (as whole number)
22
+ # @return [ImpliedProbability]
23
+ def overround!(v)
24
+ self.class.new(@value * ((100 + v)/100.rationalize), id)
25
+ end
26
+
27
+ # Convert probability to percent.
28
+ # Optionally round the resulting decimal.
29
+ #
30
+ # @param n [Integer] Number of decimal places for rounding.
31
+ def to_percent(n = nil)
32
+ n ? (@value * 100).round(n) : @value * 100
33
+ end
34
+
35
+ # Round decimal value.
36
+ #
37
+ # @param n [Integer] Number of decimal places for rounding.
38
+ def round(n = 2)
39
+ @value.round(n)
40
+ end
41
+
42
+ # Check two odds for equality.
43
+ #
44
+ # @param other [ImpliedProbability]
45
+ # @return [Boolean]
46
+ def ==(other)
47
+ if other.is_a?(self.class)
48
+ @value == other.value
49
+ else
50
+ @value == other.implied_probability.value
51
+ end
52
+ end
53
+
54
+ # Compare two odds against each other.
55
+ #
56
+ # @param other [ImpliedProbability]
57
+ # @return [-1,0,1]
58
+ def <=>(other)
59
+ value <=> other.value
60
+ end
61
+
62
+ # Returns self. This creates a consistent API for all odds.
63
+ #
64
+ # @return [self]
65
+ def implied_probability
66
+ self
67
+ end
68
+
69
+ # Convert to American odds, returning a new object.
70
+ #
71
+ # @return [American]
72
+ def american
73
+ @american ||= American.new(calculate_american, id)
74
+ end
75
+
76
+ # Convert to decimal odds, returning a new object.
77
+ #
78
+ # @return [Decimal]
79
+ def decimal
80
+ @decimal ||= Decimal.new(calculate_decimal, id)
81
+ end
82
+
83
+ # Convert to fractional odds, returning a new object.
84
+ #
85
+ # @return [Fractional]
86
+ def fractional
87
+ @fractional ||= Fractional.new(calculate_fractional, id)
88
+ end
89
+
90
+ private
91
+
92
+ # Allows for implied probability to create a vigged/unvigged odd and convert it back to source format.
93
+ def type
94
+ :implied_probability
95
+ end
96
+
97
+ # Calculate American odds value.
98
+ #
99
+ # @return [Integer, Float]
100
+ def calculate_american
101
+ value = @value.rationalize
102
+ if value >= 0.5
103
+ - ( value / (1 - value)) * 100
104
+ else
105
+ ((1 - value) / value ) * 100
106
+ end
107
+ end
108
+
109
+ # Calculate decimal odds value.
110
+ #
111
+ # @return [Float]
112
+ def calculate_decimal
113
+ 1.0 / @value
114
+ end
115
+
116
+ # Calculate fractional odds value.
117
+ #
118
+ # @return [Rational]
119
+ def calculate_fractional
120
+ (1.0 / @value - 1).rationalize
121
+ end
122
+
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,3 @@
1
+ module Oddsmaker
2
+ VERSION = "0.1.0" # :nodoc:
3
+ end
@@ -0,0 +1,44 @@
1
+ module Oddsmaker
2
+ # Wager represents an odd and a wagered amount.
3
+ # Odds can directly calculate their profit, so this is just a convenience class.
4
+ class Wager
5
+ attr_reader :amount, :odd
6
+
7
+ def initialize(amount, odd)
8
+ @amount = amount
9
+ @odd = odd
10
+ end
11
+
12
+ # Calculate profit for a wager.
13
+ #
14
+ # @return [Float, Integer]
15
+ def profit
16
+ @profit ||= odd.profit(@amount)
17
+ end
18
+
19
+ # Calculate return for a wager.
20
+ # Return is profit plus wager amount.
21
+ #
22
+ # @return [Float, Integer]
23
+ def return
24
+ @return ||= profit + @amount
25
+ end
26
+
27
+ # Hash representation of the wager.
28
+ # @return [Hash]
29
+ def to_h
30
+ {
31
+ amount: self.amount.to_f,
32
+ profit: self.profit.to_f,
33
+ return: self.return.to_f,
34
+ odd: odd.to_h,
35
+ }
36
+ end
37
+
38
+ # JSON representation of the wager.
39
+ # @return [String]
40
+ def to_json
41
+ to_h.to_json
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "oddsmaker/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "oddsmaker"
8
+ spec.version = Oddsmaker::VERSION
9
+ spec.authors = ["Paul Hoffer"]
10
+ spec.email = ["git@paulhoffer.com"]
11
+
12
+ spec.summary = %q{Calculate and convert sportsbook betting odds, probabilities, wagers, and collections of odds.}
13
+ spec.description = %q{Convert and manipulate sportsbook betting odds (American, decimal, fractional) and probabilities. Calculate wagering profit and return, and calculate total probability and vig for a collection of odds.}
14
+ spec.homepage = "https://www.github.com/phoffer/oddsmaker"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.15"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ spec.add_development_dependency "simplecov"
28
+ spec.add_development_dependency "pry"
29
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oddsmaker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Hoffer
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-10-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: simplecov
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Convert and manipulate sportsbook betting odds (American, decimal, fractional)
84
+ and probabilities. Calculate wagering profit and return, and calculate total probability
85
+ and vig for a collection of odds.
86
+ email:
87
+ - git@paulhoffer.com
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - ".gitignore"
93
+ - ".travis.yml"
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - bin/console
99
+ - bin/setup
100
+ - lib/oddsmaker.rb
101
+ - lib/oddsmaker/market.rb
102
+ - lib/oddsmaker/odd.rb
103
+ - lib/oddsmaker/odd/american.rb
104
+ - lib/oddsmaker/odd/base.rb
105
+ - lib/oddsmaker/odd/decimal.rb
106
+ - lib/oddsmaker/odd/fractional.rb
107
+ - lib/oddsmaker/odd/implied_probability.rb
108
+ - lib/oddsmaker/version.rb
109
+ - lib/oddsmaker/wager.rb
110
+ - oddsmaker.gemspec
111
+ homepage: https://www.github.com/phoffer/oddsmaker
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.6.11
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Calculate and convert sportsbook betting odds, probabilities, wagers, and
135
+ collections of odds.
136
+ test_files: []