rodders 3.0.1 → 3.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/Gemfile +7 -0
- data/Gemfile.lock +14 -0
- data/VERSION +1 -1
- data/lib/fixed_odds.rb +9 -15
- data/lib/mutually_exclusive_collection.rb +6 -10
- data/rodders.gemspec +12 -3
- data/spec/fixed_odds_spec.rb +10 -10
- data/spec/mutually_exclusive_collection_spec.rb +14 -14
- metadata +52 -4
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,6 +1,17 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
activesupport (3.2.8)
|
5
|
+
i18n (~> 0.6)
|
6
|
+
multi_json (~> 1.0)
|
7
|
+
columnize (0.3.6)
|
8
|
+
debugger (1.1.3)
|
9
|
+
columnize (>= 0.3.1)
|
10
|
+
debugger-linecache (~> 1.1.1)
|
11
|
+
debugger-ruby_core_source (~> 1.1.2)
|
12
|
+
debugger-linecache (1.1.1)
|
13
|
+
debugger-ruby_core_source (>= 1.1.1)
|
14
|
+
debugger-ruby_core_source (1.1.2)
|
4
15
|
diff-lcs (1.1.3)
|
5
16
|
git (1.2.5)
|
6
17
|
i18n (0.6.0)
|
@@ -32,7 +43,10 @@ PLATFORMS
|
|
32
43
|
ruby
|
33
44
|
|
34
45
|
DEPENDENCIES
|
46
|
+
activesupport
|
35
47
|
bundler (~> 1.0.0)
|
48
|
+
debugger
|
49
|
+
i18n
|
36
50
|
jeweler (~> 1.6.4)
|
37
51
|
money
|
38
52
|
rspec (~> 2.9.0)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.0
|
1
|
+
3.1.0
|
data/lib/fixed_odds.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'money'
|
2
|
+
require 'active_support/all'
|
2
3
|
|
3
4
|
# Represents fixed odds used in gambling and betting. Supports fractional,
|
4
5
|
# moneyline and decimal representations. Can calculate the profit
|
@@ -58,11 +59,11 @@ class FixedOdds
|
|
58
59
|
# @return (see FixedOdds.from_s)
|
59
60
|
def FixedOdds.fractional_odds fractional
|
60
61
|
raise %{could not parse "#{fractional}" as fractional odds} unless self.fractional_odds?(fractional)
|
61
|
-
return new(1.to_r) if fractional
|
62
|
+
return new(1.to_r) if fractional.inquiry.evens? or fractional == 'even money'
|
62
63
|
|
63
64
|
o = /(?<numerator>\d+)(\/|-to-)(?<denominator>\d+)/.match fractional
|
64
65
|
r = Rational o[:numerator], o[:denominator]
|
65
|
-
r = r
|
66
|
+
r = 1 / r if fractional.ends_with? ' on'
|
66
67
|
new r
|
67
68
|
end
|
68
69
|
|
@@ -80,9 +81,12 @@ class FixedOdds
|
|
80
81
|
# @return (see FixedOdds.from_s)
|
81
82
|
def FixedOdds.moneyline_odds moneyline
|
82
83
|
raise %{could not parse "#{moneyline}" as moneyline odds} unless self.moneyline_odds?(moneyline)
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
|
85
|
+
case moneyline[0]
|
86
|
+
when '+'
|
87
|
+
new(Rational(moneyline, 100))
|
88
|
+
else
|
89
|
+
new(Rational(100, -moneyline.to_i))
|
86
90
|
end
|
87
91
|
end
|
88
92
|
|
@@ -158,14 +162,4 @@ class FixedOdds
|
|
158
162
|
def <=> other
|
159
163
|
other.fractional_odds <=> @fractional_odds
|
160
164
|
end
|
161
|
-
end
|
162
|
-
|
163
|
-
class Rational
|
164
|
-
# calculates the reciprocal
|
165
|
-
# @example
|
166
|
-
# Rational(2/3).reciprocal #=> Rational(3/2)
|
167
|
-
# @return [Rational] the reciprocal
|
168
|
-
def reciprocal
|
169
|
-
1 / self
|
170
|
-
end
|
171
165
|
end
|
@@ -4,7 +4,7 @@
|
|
4
4
|
class MutuallyExclusiveCollection
|
5
5
|
# create a new collection with the given odds
|
6
6
|
# @param [Array<FixedOdds>] mutually_exclusive_outcome_odds the odds for all the mutually exclusive outcomes
|
7
|
-
def initialize
|
7
|
+
def initialize mutually_exclusive_outcome_odds
|
8
8
|
@mutually_exclusive_outcome_odds = mutually_exclusive_outcome_odds.sort
|
9
9
|
end
|
10
10
|
|
@@ -43,31 +43,27 @@ class MutuallyExclusiveCollection
|
|
43
43
|
# @return [Number] the bookmaker's return rate as a percentage
|
44
44
|
def bookmakers_return_rate
|
45
45
|
fs = fractions
|
46
|
-
|
46
|
+
1 - fs.reduce(:*) / fs.reduce(:+) if fs.any?
|
47
47
|
end
|
48
48
|
|
49
49
|
# hash of the odds and what percentage of the total stake should go on each
|
50
50
|
# @return [Hash<FixedOdds, Number>] hash of odds to percentages
|
51
51
|
def percentages
|
52
|
-
hash =
|
53
|
-
@mutually_exclusive_outcome_odds.each {|odds| hash[odds] = 1 / odds.fractional_odds / sum_inverse_outcome }
|
54
|
-
hash
|
52
|
+
@mutually_exclusive_outcome_odds.each_with_object({}) {|odds, hash| hash[odds] = 1 / odds.fractional_odds / sum_inverse_outcome }
|
55
53
|
end
|
56
54
|
|
57
55
|
# hash of the odds and what stakes to put on each given a total stake
|
58
56
|
# @param [Money] total_stake the money to distribute on the outcomes
|
59
57
|
# @return [Hash<FixedOdds, Money>] hash of odds to stakes
|
60
58
|
def stakes_for_total_stake total_stake
|
61
|
-
hash =
|
62
|
-
@mutually_exclusive_outcome_odds.each {|odds| hash[odds] = total_stake / odds.fractional_odds / sum_inverse_outcome }
|
63
|
-
hash
|
59
|
+
@mutually_exclusive_outcome_odds.each_with_object({}) {|odds, hash| hash[odds] = total_stake / odds.fractional_odds / sum_inverse_outcome }
|
64
60
|
end
|
65
61
|
|
66
62
|
# hash of the odds and the stakes needed to make the specified profit
|
67
63
|
# @param (see #stakes_for_total_stake)
|
68
64
|
# @return (see #stakes_for_total_stake)
|
69
65
|
def stakes_for_profit desired_profit
|
70
|
-
stakes_for_total_stake
|
66
|
+
stakes_for_total_stake stake_to_profit desired_profit
|
71
67
|
end
|
72
68
|
|
73
69
|
# the stake needed to win the desired profit
|
@@ -97,6 +93,6 @@ class MutuallyExclusiveCollection
|
|
97
93
|
end
|
98
94
|
|
99
95
|
def fractions
|
100
|
-
@mutually_exclusive_outcome_odds.collect
|
96
|
+
@mutually_exclusive_outcome_odds.collect &:fractional_odds
|
101
97
|
end
|
102
98
|
end
|
data/rodders.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "rodders"
|
8
|
-
s.version = "3.0
|
8
|
+
s.version = "3.1.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Nigel Lowry"]
|
12
|
-
s.date = "2012-
|
12
|
+
s.date = "2012-11-11"
|
13
13
|
s.description = "Converts between fractional, decimal and moneyline odds for betting and gambling. Calculates how much can be won on a given stake."
|
14
14
|
s.email = "nigel-lowry@ultra.eclipse.co.uk"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -36,7 +36,7 @@ Gem::Specification.new do |s|
|
|
36
36
|
s.homepage = "http://github.com/nigel-lowry/rodders"
|
37
37
|
s.licenses = ["MIT"]
|
38
38
|
s.require_paths = ["lib"]
|
39
|
-
s.rubygems_version = "1.8.
|
39
|
+
s.rubygems_version = "1.8.24"
|
40
40
|
s.summary = "Fixed odds betting library"
|
41
41
|
|
42
42
|
if s.respond_to? :specification_version then
|
@@ -44,26 +44,35 @@ Gem::Specification.new do |s|
|
|
44
44
|
|
45
45
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
46
46
|
s.add_runtime_dependency(%q<money>, [">= 0"])
|
47
|
+
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
|
48
|
+
s.add_runtime_dependency(%q<i18n>, [">= 0"])
|
47
49
|
s.add_development_dependency(%q<rspec>, ["~> 2.9.0"])
|
48
50
|
s.add_development_dependency(%q<yard>, ["~> 0.6.0"])
|
49
51
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
50
52
|
s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
|
51
53
|
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
54
|
+
s.add_development_dependency(%q<debugger>, [">= 0"])
|
52
55
|
else
|
53
56
|
s.add_dependency(%q<money>, [">= 0"])
|
57
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
58
|
+
s.add_dependency(%q<i18n>, [">= 0"])
|
54
59
|
s.add_dependency(%q<rspec>, ["~> 2.9.0"])
|
55
60
|
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
56
61
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
62
|
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
58
63
|
s.add_dependency(%q<simplecov>, [">= 0"])
|
64
|
+
s.add_dependency(%q<debugger>, [">= 0"])
|
59
65
|
end
|
60
66
|
else
|
61
67
|
s.add_dependency(%q<money>, [">= 0"])
|
68
|
+
s.add_dependency(%q<activesupport>, [">= 0"])
|
69
|
+
s.add_dependency(%q<i18n>, [">= 0"])
|
62
70
|
s.add_dependency(%q<rspec>, ["~> 2.9.0"])
|
63
71
|
s.add_dependency(%q<yard>, ["~> 0.6.0"])
|
64
72
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
65
73
|
s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
|
66
74
|
s.add_dependency(%q<simplecov>, [">= 0"])
|
75
|
+
s.add_dependency(%q<debugger>, [">= 0"])
|
67
76
|
end
|
68
77
|
end
|
69
78
|
|
data/spec/fixed_odds_spec.rb
CHANGED
@@ -16,13 +16,13 @@ describe "FixedOdds" do
|
|
16
16
|
it "does not change the input string with 'against'" do
|
17
17
|
value = '4/1 against'
|
18
18
|
FixedOdds.fractional_odds(value)
|
19
|
-
value.end_with?('against').should
|
19
|
+
value.end_with?('against').should be
|
20
20
|
end
|
21
21
|
|
22
22
|
it "does not modify the input string with 'on'" do
|
23
23
|
value = '4/1 on'
|
24
24
|
FixedOdds.fractional_odds(value)
|
25
|
-
value.end_with?('on').should
|
25
|
+
value.end_with?('on').should be
|
26
26
|
end
|
27
27
|
|
28
28
|
specify { FixedOdds.fractional_odds('4/1 against').should == FixedOdds.fractional_odds('4/1') }
|
@@ -80,12 +80,12 @@ describe "FixedOdds" do
|
|
80
80
|
)
|
81
81
|
end
|
82
82
|
|
83
|
-
specify { FixedOdds.moneyline_odds('+400').profit_on_stake(
|
84
|
-
specify { FixedOdds.moneyline_odds('+100').profit_on_stake(
|
83
|
+
specify { FixedOdds.moneyline_odds('+400').profit_on_stake('£100'.to_money).should == '£400'.to_money }
|
84
|
+
specify { FixedOdds.moneyline_odds('+100').profit_on_stake('£100'.to_money).should == '£100'.to_money }
|
85
85
|
|
86
|
-
specify { FixedOdds.moneyline_odds('-400').profit_on_stake(
|
87
|
-
specify { FixedOdds.moneyline_odds('-100').profit_on_stake(
|
88
|
-
specify { FixedOdds.moneyline_odds('+100').profit_on_stake(
|
86
|
+
specify { FixedOdds.moneyline_odds('-400').profit_on_stake('£400'.to_money).should == '£100'.to_money }
|
87
|
+
specify { FixedOdds.moneyline_odds('-100').profit_on_stake('£100'.to_money).should == '£100'.to_money }
|
88
|
+
specify { FixedOdds.moneyline_odds('+100').profit_on_stake('£100'.to_money).should == '£100'.to_money }
|
89
89
|
end
|
90
90
|
|
91
91
|
describe ".decimal_odds" do
|
@@ -98,9 +98,9 @@ describe "FixedOdds" do
|
|
98
98
|
)
|
99
99
|
end
|
100
100
|
|
101
|
-
specify { FixedOdds.decimal_odds('2').profit_on_stake(
|
102
|
-
specify { FixedOdds.decimal_odds('5').profit_on_stake(
|
103
|
-
specify { FixedOdds.decimal_odds('1.25').profit_on_stake(
|
101
|
+
specify { FixedOdds.decimal_odds('2').profit_on_stake('£1'.to_money).should == '£1'.to_money }
|
102
|
+
specify { FixedOdds.decimal_odds('5').profit_on_stake('£1'.to_money).should == '£4'.to_money }
|
103
|
+
specify { FixedOdds.decimal_odds('1.25').profit_on_stake('£4'.to_money).should == '£1'.to_money }
|
104
104
|
end
|
105
105
|
|
106
106
|
describe ".from_s" do
|
@@ -25,7 +25,7 @@ describe "MutuallyExclusiveCollection" do
|
|
25
25
|
@bookmaker = MutuallyExclusiveCollection.new []
|
26
26
|
end
|
27
27
|
|
28
|
-
specify { @bookmaker.bookmakers_return_rate.should
|
28
|
+
specify { @bookmaker.bookmakers_return_rate.should be_nil }
|
29
29
|
end
|
30
30
|
|
31
31
|
context "decimal odds arbitrage" do
|
@@ -48,7 +48,7 @@ describe "MutuallyExclusiveCollection" do
|
|
48
48
|
specify { @bookmaker1.bookmakers_return_rate.should be_within(0.0001).of(0.0534) }
|
49
49
|
specify { @bookmaker2.bookmakers_return_rate.should be_within(0.0001).of(0.0478) }
|
50
50
|
|
51
|
-
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake(
|
51
|
+
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake('£100'.to_money).should == '£4.63'.to_money }
|
52
52
|
specify { @bookmaker_vulnerable_to_arbitrage.profit_percentage.should be_within(0.001).of(0.046) }
|
53
53
|
end
|
54
54
|
|
@@ -64,7 +64,7 @@ describe "MutuallyExclusiveCollection" do
|
|
64
64
|
|
65
65
|
specify { @bookmaker_vulnerable_to_arbitrage.should be_arbitrageable }
|
66
66
|
its(:profit_percentage) { should be_within(0.001).of(0.2) }
|
67
|
-
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake(
|
67
|
+
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake('£500'.to_money).should == '£100'.to_money }
|
68
68
|
end
|
69
69
|
|
70
70
|
context "more than two mutually exclusive events" do
|
@@ -96,27 +96,27 @@ describe "MutuallyExclusiveCollection" do
|
|
96
96
|
total = Money.parse '£500'
|
97
97
|
amounts = @bookmaker_vulnerable_to_arbitrage.stakes_for_total_stake total
|
98
98
|
amounts.should have(3).items
|
99
|
-
amounts[@odds1].should ==
|
100
|
-
amounts[@odds2].should ==
|
101
|
-
amounts[@odds3].should ==
|
99
|
+
amounts[@odds1].should == '£396.14'.to_money
|
100
|
+
amounts[@odds2].should == '£73.57'.to_money
|
101
|
+
amounts[@odds3].should == '£30.29'.to_money
|
102
102
|
amounts.values.reduce(:+).should == total
|
103
103
|
end
|
104
104
|
end
|
105
105
|
|
106
|
-
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake(
|
106
|
+
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake('£500'.to_money).should == '£14.98'.to_money }
|
107
107
|
|
108
108
|
describe "#stakes_for_profit" do
|
109
109
|
it "gives the right amounts" do
|
110
|
-
amounts = @bookmaker_vulnerable_to_arbitrage.stakes_for_profit
|
110
|
+
amounts = @bookmaker_vulnerable_to_arbitrage.stakes_for_profit '£750'.to_money
|
111
111
|
amounts.should have(3).items
|
112
|
-
amounts[@odds1].should ==
|
113
|
-
amounts[@odds2].should ==
|
114
|
-
amounts[@odds3].should ==
|
115
|
-
amounts.values.reduce(:+).should ==
|
112
|
+
amounts[@odds1].should == '£19833.33'.to_money
|
113
|
+
amounts[@odds2].should == '£3683.33'.to_money
|
114
|
+
amounts[@odds3].should == '£1516.67'.to_money
|
115
|
+
amounts.values.reduce(:+).should == '£25033.33'.to_money
|
116
116
|
end
|
117
117
|
end
|
118
118
|
|
119
|
-
specify { @bookmaker_vulnerable_to_arbitrage.stake_to_profit(
|
119
|
+
specify { @bookmaker_vulnerable_to_arbitrage.stake_to_profit('£750'.to_money).should == '£25033.33'.to_money }
|
120
120
|
end
|
121
121
|
|
122
122
|
context "different events with same odds" do
|
@@ -128,7 +128,7 @@ describe "MutuallyExclusiveCollection" do
|
|
128
128
|
end
|
129
129
|
|
130
130
|
specify { @bookmaker_vulnerable_to_arbitrage.should be_arbitrageable }
|
131
|
-
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake(
|
131
|
+
specify { @bookmaker_vulnerable_to_arbitrage.profit_on_stake('£100'.to_money).should == '£650'.to_money }
|
132
132
|
|
133
133
|
describe "#percentages" do
|
134
134
|
it "gives the percentages to put on each bet" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rodders
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0
|
4
|
+
version: 3.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-11-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: money
|
@@ -27,6 +27,38 @@ dependencies:
|
|
27
27
|
- - ! '>='
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: activesupport
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: i18n
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
30
62
|
- !ruby/object:Gem::Dependency
|
31
63
|
name: rspec
|
32
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -107,6 +139,22 @@ dependencies:
|
|
107
139
|
- - ! '>='
|
108
140
|
- !ruby/object:Gem::Version
|
109
141
|
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: debugger
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
110
158
|
description: Converts between fractional, decimal and moneyline odds for betting and
|
111
159
|
gambling. Calculates how much can be won on a given stake.
|
112
160
|
email: nigel-lowry@ultra.eclipse.co.uk
|
@@ -146,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
146
194
|
version: '0'
|
147
195
|
segments:
|
148
196
|
- 0
|
149
|
-
hash:
|
197
|
+
hash: 437333735277840555
|
150
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
151
199
|
none: false
|
152
200
|
requirements:
|
@@ -155,7 +203,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
203
|
version: '0'
|
156
204
|
requirements: []
|
157
205
|
rubyforge_project:
|
158
|
-
rubygems_version: 1.8.
|
206
|
+
rubygems_version: 1.8.24
|
159
207
|
signing_key:
|
160
208
|
specification_version: 3
|
161
209
|
summary: Fixed odds betting library
|