rodders 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +34 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +19 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/FixedOdds.rb +125 -0
- data/lib/MutuallyExclusiveCollection.rb +31 -0
- data/lib/Rational.rb +8 -0
- data/spec/FixedOdds_spec.rb +346 -0
- data/spec/MutuallyExclusiveCollection_spec.rb +29 -0
- data/spec/Rational_spec.rb +12 -0
- data/spec/spec_helper.rb +14 -0
- metadata +121 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
gem "money"
|
7
|
+
|
8
|
+
# Add dependencies to develop your gem here.
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
10
|
+
group :development do
|
11
|
+
gem "rspec", "~> 2.3.0"
|
12
|
+
gem "bundler", "~> 1.0.0"
|
13
|
+
gem "jeweler", "~> 1.6.4"
|
14
|
+
gem "rcov", ">= 0"
|
15
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.3)
|
5
|
+
git (1.2.5)
|
6
|
+
i18n (0.6.0)
|
7
|
+
jeweler (1.6.4)
|
8
|
+
bundler (~> 1.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
rake
|
11
|
+
json (1.6.1)
|
12
|
+
money (4.0.1)
|
13
|
+
i18n (~> 0.4)
|
14
|
+
json
|
15
|
+
rake (0.9.2.2)
|
16
|
+
rcov (0.9.11)
|
17
|
+
rspec (2.3.0)
|
18
|
+
rspec-core (~> 2.3.0)
|
19
|
+
rspec-expectations (~> 2.3.0)
|
20
|
+
rspec-mocks (~> 2.3.0)
|
21
|
+
rspec-core (2.3.1)
|
22
|
+
rspec-expectations (2.3.0)
|
23
|
+
diff-lcs (~> 1.1.2)
|
24
|
+
rspec-mocks (2.3.0)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
bundler (~> 1.0.0)
|
31
|
+
jeweler (~> 1.6.4)
|
32
|
+
money
|
33
|
+
rcov
|
34
|
+
rspec (~> 2.3.0)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Nigel Lowry
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
= rodders
|
2
|
+
|
3
|
+
Performs calculations on fixed-odds, converting between different representations, and works out how much can be won given a stake.
|
4
|
+
|
5
|
+
== Contributing to rodders
|
6
|
+
|
7
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
8
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
9
|
+
* Fork the project
|
10
|
+
* Start a feature/bugfix branch
|
11
|
+
* Commit and push until you are happy with your contribution
|
12
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
13
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2011 Nigel Lowry. See LICENSE.txt for
|
18
|
+
further details.
|
19
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "rodders"
|
18
|
+
gem.homepage = "http://github.com/nigel-lowry/rodders"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Fixed odds betting library}
|
21
|
+
gem.description = %Q{Converts between fractional, decimal and moneyline odds. Can be used to calculate how much can be won on a bet given a stake}
|
22
|
+
gem.email = "nigel-lowry@ultra.eclipse.co.uk"
|
23
|
+
gem.authors = ["Nigel Lowry"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rake/rdoctask'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "rodders #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/FixedOdds.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'money'
|
2
|
+
|
3
|
+
# Represents fixed odds used in gambling and betting. Supports fractional,
|
4
|
+
# moneyline and decimal representations. Can calculate the profit
|
5
|
+
# and return on a winning bet.
|
6
|
+
class FixedOdds
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
# the fractional odds as a Rational
|
10
|
+
attr_reader :fractional_odds
|
11
|
+
|
12
|
+
# creates a new FixedOdds from a string which can be in fractional,
|
13
|
+
# moneyline or decimal format.
|
14
|
+
def FixedOdds.from_s(odds)
|
15
|
+
case
|
16
|
+
when FixedOdds.fractional_odds?(odds) then FixedOdds.fractional_odds odds
|
17
|
+
when FixedOdds.moneyline_odds?(odds) then FixedOdds.moneyline_odds odds
|
18
|
+
when FixedOdds.decimal_odds?(odds) then FixedOdds.decimal_odds odds
|
19
|
+
else raise ArgumentError, %{could not parse "#{odds}"}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# tells if the odds are in fractional form
|
24
|
+
def FixedOdds.fractional_odds?(odds)
|
25
|
+
odds =~ /\d+\/\d+|\d+-to-\d+|evens|even money/
|
26
|
+
end
|
27
|
+
|
28
|
+
# tells if the odds are in moneyline form
|
29
|
+
def FixedOdds.moneyline_odds?(odds)
|
30
|
+
odds =~ /[+-]\d+/
|
31
|
+
end
|
32
|
+
|
33
|
+
# tells if the odds are in decimal form
|
34
|
+
def FixedOdds.decimal_odds?(odds)
|
35
|
+
odds =~ /^(\d+|\d+\.\d+|\.\d+)/
|
36
|
+
end
|
37
|
+
|
38
|
+
# creates a new FixedOdds from fractional form. These can be in the form
|
39
|
+
# * 4-to-1
|
40
|
+
# * 4-to-1 against
|
41
|
+
# * 4-to-1 on
|
42
|
+
# * 4/1
|
43
|
+
# * 4/1 against
|
44
|
+
# * 4/1 on
|
45
|
+
# * evens
|
46
|
+
# * even money
|
47
|
+
def FixedOdds.fractional_odds(fractional)
|
48
|
+
raise %{could not parse "#{fractional}" as fractional odds} unless FixedOdds.fractional_odds?(fractional)
|
49
|
+
return new(Rational('1/1')) if fractional == 'evens' || fractional == 'even money'
|
50
|
+
if /(?<numerator>\d+)(\/|-to-)(?<denominator>\d+)/ =~ fractional then r = Rational("#{numerator}/#{denominator}") end
|
51
|
+
r = r.reciprocal if fractional.end_with? ' on'
|
52
|
+
new(Rational(r))
|
53
|
+
end
|
54
|
+
|
55
|
+
# creates a new FixedOdds from a Rational
|
56
|
+
def initialize(fractional_odds)
|
57
|
+
@fractional_odds = fractional_odds
|
58
|
+
end
|
59
|
+
|
60
|
+
# creates a new FixedOdds from moneyline form. Examples are
|
61
|
+
# * +400
|
62
|
+
# * -500
|
63
|
+
def FixedOdds.moneyline_odds(moneyline)
|
64
|
+
raise %{could not parse "#{moneyline}" as moneyline odds} unless FixedOdds.moneyline_odds?(moneyline)
|
65
|
+
sign = moneyline[0]
|
66
|
+
if sign == '+' then new(Rational("#{moneyline}/100"))
|
67
|
+
else new(Rational("100/#{moneyline.to_i.magnitude}"))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# creates a new FixedOdds from decimal form. Examples are
|
72
|
+
# * 1.25
|
73
|
+
# * 2
|
74
|
+
def FixedOdds.decimal_odds(decimal)
|
75
|
+
raise %{could not parse "#{decimal}" as decimal odds} unless FixedOdds.decimal_odds?(decimal)
|
76
|
+
new(Rational(decimal.to_f - 1))
|
77
|
+
end
|
78
|
+
|
79
|
+
# calculates the profit won on a winning _stake_
|
80
|
+
def profit_on_winning_stake(stake)
|
81
|
+
stake.to_money * @fractional_odds
|
82
|
+
end
|
83
|
+
|
84
|
+
# calculates the total return on a winning _stake_
|
85
|
+
# (which is the profit plus the initial stake)
|
86
|
+
def total_return_on_winning_stake(stake)
|
87
|
+
profit_on_winning_stake(stake) + stake.to_money
|
88
|
+
end
|
89
|
+
|
90
|
+
# string representation in fractional form such as '4/1'
|
91
|
+
def to_s
|
92
|
+
to_s_fractional
|
93
|
+
end
|
94
|
+
|
95
|
+
# string representation in fractional form such as '4/1'
|
96
|
+
def to_s_fractional
|
97
|
+
@fractional_odds.to_s
|
98
|
+
end
|
99
|
+
|
100
|
+
# string representation in moneyline form
|
101
|
+
def to_s_moneyline
|
102
|
+
integral_number_with_sign_regex = "%+d"
|
103
|
+
|
104
|
+
if @fractional_odds > 1.0
|
105
|
+
integral_number_with_sign_regex % (fractional_odds * 100).to_i
|
106
|
+
else
|
107
|
+
integral_number_with_sign_regex % (-100.0 / fractional_odds)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# string representation in decimal form
|
112
|
+
def to_s_decimal
|
113
|
+
"%g" % (fractional_odds + 1)
|
114
|
+
end
|
115
|
+
|
116
|
+
def ==(other)
|
117
|
+
other.fractional_odds == @fractional_odds
|
118
|
+
end
|
119
|
+
|
120
|
+
# low odds are those which pay out the most money
|
121
|
+
# on a winning bet and vice-versa
|
122
|
+
def <=>(other)
|
123
|
+
other.fractional_odds <=> @fractional_odds
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# A collection of mutually exclusive events.
|
2
|
+
#--
|
3
|
+
# The events are not currently enforced as being
|
4
|
+
# mutually exclusive so are really just plain
|
5
|
+
# old events right now.
|
6
|
+
class MutuallyExclusiveCollection
|
7
|
+
# create a new collection with the given events
|
8
|
+
def initialize(events)
|
9
|
+
@events = events.sort
|
10
|
+
end
|
11
|
+
|
12
|
+
# the least likely of the events to occur
|
13
|
+
def underdog
|
14
|
+
@events.first
|
15
|
+
end
|
16
|
+
|
17
|
+
# the most likely of the events to occur
|
18
|
+
def favorite
|
19
|
+
@events.last
|
20
|
+
end
|
21
|
+
|
22
|
+
# the events in ascending order of probability
|
23
|
+
def in_ascending_probability
|
24
|
+
@events
|
25
|
+
end
|
26
|
+
|
27
|
+
# the events in descending order of probability
|
28
|
+
def in_descending_probability
|
29
|
+
@events.reverse
|
30
|
+
end
|
31
|
+
end
|
data/lib/Rational.rb
ADDED
@@ -0,0 +1,346 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "FixedOdds" do
|
4
|
+
|
5
|
+
describe "fractional_odds factory" do
|
6
|
+
it "should raise error if not fractional odds" do
|
7
|
+
expect {
|
8
|
+
FixedOdds.fractional_odds '-400'
|
9
|
+
}.to raise_error(
|
10
|
+
RuntimeError,
|
11
|
+
/could not parse "-400" as fractional odds/
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should not modify the input string with 'against'" do
|
16
|
+
value = '4/1 against'
|
17
|
+
FixedOdds.fractional_odds(value)
|
18
|
+
value.end_with?('against').should == true
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should not modify the input string with 'on'" do
|
22
|
+
value = '4/1 on'
|
23
|
+
FixedOdds.fractional_odds(value)
|
24
|
+
value.end_with?('on').should == true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should treat '4/1 against' the same as '4/1'" do
|
28
|
+
FixedOdds.fractional_odds('4/1 against').should == FixedOdds.fractional_odds('4/1')
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should treat '4/1 on' the same as '1/4'" do
|
32
|
+
FixedOdds.fractional_odds('4/1 on').should == FixedOdds.fractional_odds('1/4')
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should treat 'evens' as '1/1'" do
|
36
|
+
FixedOdds.fractional_odds('evens').should == FixedOdds.fractional_odds('1/1')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should treat 'even money' as '1/1'" do
|
40
|
+
FixedOdds.fractional_odds('even money').should == FixedOdds.fractional_odds('1/1')
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should recognise '4-to-1' as '4/1'" do
|
44
|
+
FixedOdds.fractional_odds('4-to-1').should == FixedOdds.fractional_odds('4/1')
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should recognise '4-to-1 against' as '4/1'" do
|
48
|
+
FixedOdds.fractional_odds('4-to-1 against').should == FixedOdds.fractional_odds('4/1')
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should recognise '4-to-1 on' as '1/4'" do
|
52
|
+
FixedOdds.fractional_odds('4-to-1 on').should == FixedOdds.fractional_odds('1/4')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "moneyline_odds factory" do
|
57
|
+
it "should raise error if not moneyline odds" do
|
58
|
+
expect {
|
59
|
+
FixedOdds.moneyline_odds '1.25'
|
60
|
+
}.to raise_error(
|
61
|
+
RuntimeError,
|
62
|
+
/could not parse "1.25" as moneyline odds/
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "positive figures" do
|
67
|
+
it "should treat '+400' as meaning winning $400 on a $100 bet" do
|
68
|
+
plus400 = FixedOdds.moneyline_odds('+400')
|
69
|
+
plus400.profit_on_winning_stake('$100').should == '$400'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should treat +100 as meaning winning $100 on a $100 bet" do
|
73
|
+
plus100 = FixedOdds.moneyline_odds('+100')
|
74
|
+
plus100.profit_on_winning_stake('$100').should == '$100'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "negative figures" do
|
79
|
+
it "should treat '-400' as meaning you need to wager $400 to win $100" do
|
80
|
+
minus400 = FixedOdds.moneyline_odds('-400')
|
81
|
+
minus400.profit_on_winning_stake('$400').should == '$100'
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should treat '-100' as meaning you need to wager $100 to win $100 (which is identical to '+100')" do
|
85
|
+
minus100 = FixedOdds.moneyline_odds('-100')
|
86
|
+
minus100.profit_on_winning_stake('$100').should == '$100'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "decimal_odds factory" do
|
92
|
+
it "should raise error if not decimal odds" do
|
93
|
+
expect {
|
94
|
+
FixedOdds.decimal_odds '-400'
|
95
|
+
}.to raise_error(
|
96
|
+
RuntimeError,
|
97
|
+
/could not parse "-400" as decimal odds/
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should treat '2' as meaning you have to wager $100 to win $100" do
|
102
|
+
d2 = FixedOdds.decimal_odds('2')
|
103
|
+
d2.profit_on_winning_stake('$100').should == '$100'
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should treat '5' as meaning you have to wager $100 to win $400" do
|
107
|
+
d5 = FixedOdds.decimal_odds('5')
|
108
|
+
d5.profit_on_winning_stake('$100').should == '$400'
|
109
|
+
end
|
110
|
+
|
111
|
+
it "should treat '1.25' as meaning yo have to wager $400 to win $100" do
|
112
|
+
d1_25 = FixedOdds.decimal_odds('1.25')
|
113
|
+
d1_25.profit_on_winning_stake('$400').should == '$100'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#from_s" do
|
118
|
+
describe "bad input" do
|
119
|
+
it "should reject garbage" do
|
120
|
+
expect {
|
121
|
+
FixedOdds.from_s('garbage')
|
122
|
+
}.to raise_error(
|
123
|
+
ArgumentError,
|
124
|
+
/could not parse "garbage"/
|
125
|
+
)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should reject an empty string" do
|
130
|
+
expect {
|
131
|
+
FixedOdds.from_s('')
|
132
|
+
}.to raise_error(
|
133
|
+
ArgumentError,
|
134
|
+
/could not parse ""/
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
describe "fractional odds" do
|
139
|
+
it "should parse '4/1'" do
|
140
|
+
FixedOdds.from_s('4/1').should == FixedOdds.fractional_odds('4/1')
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should parse 'evens'" do
|
144
|
+
FixedOdds.from_s('evens').should == FixedOdds.fractional_odds('1/1')
|
145
|
+
end
|
146
|
+
|
147
|
+
it "should parse 'even money'" do
|
148
|
+
FixedOdds.from_s('even money').should == FixedOdds.fractional_odds('1/1')
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should parse '4/1 against'" do
|
152
|
+
FixedOdds.from_s('4/1 against').should == FixedOdds.fractional_odds('4/1')
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should parse '4/1 on'" do
|
156
|
+
FixedOdds.from_s('4/1 on').should == FixedOdds.fractional_odds('1/4')
|
157
|
+
end
|
158
|
+
|
159
|
+
it "should parse '4-to-1'" do
|
160
|
+
FixedOdds.from_s('4-to-1').should == FixedOdds.fractional_odds('4/1')
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should parse '4-to-1 against'" do
|
164
|
+
FixedOdds.from_s('4-to-1 against').should == FixedOdds.fractional_odds('4/1')
|
165
|
+
end
|
166
|
+
|
167
|
+
it "should parse '4-to-1 on'" do
|
168
|
+
FixedOdds.from_s('4-to-1 on').should == FixedOdds.fractional_odds('1/4')
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should raise an error for a zero denominator" do
|
172
|
+
expect {
|
173
|
+
FixedOdds.from_s('4/0')
|
174
|
+
}.to raise_error(
|
175
|
+
ZeroDivisionError
|
176
|
+
)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "moneyline odds" do
|
181
|
+
it "should parse positive moneyline odds" do
|
182
|
+
FixedOdds.from_s('+400').should == FixedOdds.moneyline_odds('+400')
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should parse negative moneyline odds" do
|
186
|
+
FixedOdds.from_s('-400').should == FixedOdds.moneyline_odds('-400')
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "decimal odds" do
|
191
|
+
it "should parse integral odds" do
|
192
|
+
FixedOdds.from_s('2').should == FixedOdds.decimal_odds('2')
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should parse floating-point odds" do
|
196
|
+
FixedOdds.from_s('1.25').should == FixedOdds.decimal_odds('1.25')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
describe "#==" do
|
202
|
+
it "should treat similar fractions equally" do
|
203
|
+
FixedOdds.fractional_odds('100/30').should == FixedOdds.fractional_odds('10/3')
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should recognise '1/1' and '2' are the same" do
|
207
|
+
FixedOdds.fractional_odds('1/1').should == FixedOdds.decimal_odds('2')
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should recognise '4/1' and '5' are the same" do
|
211
|
+
FixedOdds.fractional_odds('4/1').should == FixedOdds.decimal_odds('5')
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should recognise '1/4' and '1.25' are the same" do
|
215
|
+
FixedOdds.fractional_odds('1/4').should == FixedOdds.decimal_odds('1.25')
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should recognise '4/1' and '+400' are the same" do
|
219
|
+
FixedOdds.fractional_odds('4/1').should == FixedOdds.moneyline_odds('+400')
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should recognise '1/4' and '-400' are the same" do
|
223
|
+
FixedOdds.fractional_odds('1/4').should == FixedOdds.moneyline_odds('-400')
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should recognise '+100' and '-100' are the same" do
|
227
|
+
FixedOdds.moneyline_odds('+100').should == FixedOdds.moneyline_odds('-100')
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe "#to_s" do
|
232
|
+
it "should display the odds in fractional odds format" do
|
233
|
+
FixedOdds.from_s('+400').to_s.should == '4/1'
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "#to_s_fractional" do
|
238
|
+
it "should display '4/1' as '4/1'" do
|
239
|
+
FixedOdds.fractional_odds('4/1').to_s_fractional.should == '4/1'
|
240
|
+
end
|
241
|
+
|
242
|
+
it "should print out '100/30' as '10/3' in lowest terms" do
|
243
|
+
FixedOdds.fractional_odds('100/30').to_s_fractional.should == '10/3'
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should display '+400' as '4/1'" do
|
247
|
+
FixedOdds.moneyline_odds('+400').to_s_fractional.should == '4/1'
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should display '5' as '4/1'" do
|
251
|
+
FixedOdds.decimal_odds('5').to_s_fractional.should == '4/1'
|
252
|
+
end
|
253
|
+
|
254
|
+
it "should display '+100' as '1/1'" do
|
255
|
+
FixedOdds.moneyline_odds('+100').to_s_fractional.should == '1/1'
|
256
|
+
end
|
257
|
+
|
258
|
+
it "should display '-100' as '1/1'" do
|
259
|
+
FixedOdds.moneyline_odds('-100').to_s_fractional.should == '1/1'
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
describe "#to_s_moneyline" do
|
264
|
+
it "should display '+400' as '+400'" do
|
265
|
+
FixedOdds.moneyline_odds('+400').to_s_moneyline.should == ('+400')
|
266
|
+
end
|
267
|
+
|
268
|
+
it "should display '+100' as '-100' (but this could have equally been '+100')" do
|
269
|
+
FixedOdds.moneyline_odds('+100').to_s_moneyline.should == ('-100')
|
270
|
+
end
|
271
|
+
|
272
|
+
it "should display '-100' as '-100' (but this could have equally been '+100')" do
|
273
|
+
FixedOdds.moneyline_odds('-100').to_s_moneyline.should == ('-100')
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should display '4/1' as '+400'" do
|
277
|
+
FixedOdds.fractional_odds('4/1').to_s_moneyline.should == '+400'
|
278
|
+
end
|
279
|
+
|
280
|
+
it "should display '5' as '+400'" do
|
281
|
+
FixedOdds.decimal_odds('5').to_s_moneyline.should == '+400'
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should display '1.25' as '-400'" do
|
285
|
+
FixedOdds.decimal_odds('1.25').to_s_moneyline.should == '-400'
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should display a floating point moneyline"
|
289
|
+
end
|
290
|
+
|
291
|
+
describe "#to_s_decimal" do
|
292
|
+
it "should display '1.25' as '1.25'" do
|
293
|
+
FixedOdds.decimal_odds('1.25').to_s_decimal.should == '1.25'
|
294
|
+
end
|
295
|
+
|
296
|
+
it "should display '1/4' as '1.25'" do
|
297
|
+
FixedOdds.fractional_odds('1/4').to_s_decimal.should == '1.25'
|
298
|
+
end
|
299
|
+
|
300
|
+
it "should display '-400' as '1.25'" do
|
301
|
+
FixedOdds.moneyline_odds('-400').to_s_decimal.should == '1.25'
|
302
|
+
end
|
303
|
+
|
304
|
+
it "should display '+100' as '2'" do
|
305
|
+
FixedOdds.moneyline_odds('+100').to_s_decimal.should == '2'
|
306
|
+
end
|
307
|
+
|
308
|
+
it "should display '-100' as '2'" do
|
309
|
+
FixedOdds.moneyline_odds('-100').to_s_decimal.should == '2'
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
describe "#profit_on_winning_stake" do
|
314
|
+
it "should return a profit of $400 on a $100 stake on a 4/1 bet" do
|
315
|
+
fourToOne = FixedOdds.fractional_odds '4/1'
|
316
|
+
fourToOne.profit_on_winning_stake('$100').should == '$400'
|
317
|
+
end
|
318
|
+
|
319
|
+
it "should return a profit of $25 on a $110 stake with a 1/4 bet" do
|
320
|
+
oneToFour = FixedOdds.fractional_odds '1/4'
|
321
|
+
oneToFour.profit_on_winning_stake('$100').should == '$25'
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
describe "#total_return_on_winning_stake" do
|
326
|
+
it "should show that the full amount back on a winning 4/1 bet with a $100 stake is $500" do
|
327
|
+
fourToOne = FixedOdds.fractional_odds '4/1'
|
328
|
+
fourToOne.total_return_on_winning_stake('$100').should == '$500'
|
329
|
+
end
|
330
|
+
|
331
|
+
it "should show that the full amount back on a winning 1/4 bet with a $100 stake is $125" do
|
332
|
+
oneToFour = FixedOdds.fractional_odds '1/4'
|
333
|
+
oneToFour.total_return_on_winning_stake('$100').should == '$125'
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
describe "object comparison" do
|
338
|
+
it "'+915' is less likely than '-275'" do
|
339
|
+
FixedOdds.from_s('+915').should be < FixedOdds.from_s('-275')
|
340
|
+
end
|
341
|
+
|
342
|
+
it "'-275' is more likely than '+915'" do
|
343
|
+
FixedOdds.from_s('-275').should be > FixedOdds.from_s('+915')
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "MutuallyExclusiveCollection" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@good_team = FixedOdds.from_s '-275'
|
7
|
+
@draw = FixedOdds.from_s '+429'
|
8
|
+
@bad_team = FixedOdds.from_s '+915'
|
9
|
+
|
10
|
+
@events = MutuallyExclusiveCollection.new [@draw, @bad_team, @good_team]
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should return the bad team as the underdog" do
|
14
|
+
@events.underdog.should == @bad_team
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return the good team as the favorite" do
|
18
|
+
@events.favorite.should == @good_team
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return the events in descending probability" do
|
22
|
+
@events.in_descending_probability.should == [@good_team, @draw, @bad_team]
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return the events in ascending probability" do
|
26
|
+
@events.in_ascending_probability.should == [@bad_team, @draw, @good_team]
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "Rational" do
|
4
|
+
describe "#reciprocal" do
|
5
|
+
it "should invert the numerator and denominator" do
|
6
|
+
rational = Rational('1/2')
|
7
|
+
inverted = rational.reciprocal
|
8
|
+
inverted.should == Rational('2/1')
|
9
|
+
rational.should == Rational('1/2')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'FixedOdds'
|
5
|
+
require 'Rational'
|
6
|
+
require 'MutuallyExclusiveCollection'
|
7
|
+
|
8
|
+
# Requires supporting files with custom matchers and macros, etc,
|
9
|
+
# in ./support/ and its subdirectories.
|
10
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
11
|
+
|
12
|
+
RSpec.configure do |config|
|
13
|
+
|
14
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rodders
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Nigel Lowry
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-06 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: money
|
16
|
+
requirement: &70188460564980 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70188460564980
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &70188460563540 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.3.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70188460563540
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: bundler
|
38
|
+
requirement: &70188460562320 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.0.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70188460562320
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: jeweler
|
49
|
+
requirement: &70188460561160 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.6.4
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70188460561160
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rcov
|
60
|
+
requirement: &70188460559840 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70188460559840
|
69
|
+
description: Converts between fractional, decimal and moneyline odds. Can be used
|
70
|
+
to calculate how much can be won on a bet given a stake
|
71
|
+
email: nigel-lowry@ultra.eclipse.co.uk
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files:
|
75
|
+
- LICENSE.txt
|
76
|
+
- README.rdoc
|
77
|
+
files:
|
78
|
+
- .document
|
79
|
+
- .rspec
|
80
|
+
- Gemfile
|
81
|
+
- Gemfile.lock
|
82
|
+
- LICENSE.txt
|
83
|
+
- README.rdoc
|
84
|
+
- Rakefile
|
85
|
+
- VERSION
|
86
|
+
- lib/FixedOdds.rb
|
87
|
+
- lib/MutuallyExclusiveCollection.rb
|
88
|
+
- lib/Rational.rb
|
89
|
+
- spec/FixedOdds_spec.rb
|
90
|
+
- spec/MutuallyExclusiveCollection_spec.rb
|
91
|
+
- spec/Rational_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
homepage: http://github.com/nigel-lowry/rodders
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
segments:
|
107
|
+
- 0
|
108
|
+
hash: 3827277276823054261
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
none: false
|
111
|
+
requirements:
|
112
|
+
- - ! '>='
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 1.8.10
|
118
|
+
signing_key:
|
119
|
+
specification_version: 3
|
120
|
+
summary: Fixed odds betting library
|
121
|
+
test_files: []
|