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 ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
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,8 @@
1
+ class Rational
2
+ # calculates the reciprocal of the rational number
3
+ #--
4
+ # Would be better if wrote reciprocal! method instead
5
+ def reciprocal
6
+ 1 / self
7
+ end
8
+ end
@@ -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
@@ -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: []