rodders 3.0.1 → 3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|