rodders 3.1.0 → 3.1.1
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/VERSION +1 -1
- data/lib/mutually_exclusive_collection.rb +11 -5
- data/rodders.gemspec +2 -2
- data/spec/mutually_exclusive_collection_spec.rb +59 -72
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
3.1.
|
1
|
+
3.1.1
|
@@ -2,34 +2,40 @@
|
|
2
2
|
# Includes methods for sports betting arbitrage. Due to the discrete nature
|
3
3
|
# of real-world monetary amounts, there may be small rounding errors.
|
4
4
|
class MutuallyExclusiveCollection
|
5
|
+
include Enumerable
|
6
|
+
|
5
7
|
# create a new collection with the given odds
|
6
8
|
# @param [Array<FixedOdds>] mutually_exclusive_outcome_odds the odds for all the mutually exclusive outcomes
|
7
9
|
def initialize mutually_exclusive_outcome_odds
|
8
|
-
@mutually_exclusive_outcome_odds = mutually_exclusive_outcome_odds
|
10
|
+
@mutually_exclusive_outcome_odds = mutually_exclusive_outcome_odds
|
11
|
+
end
|
12
|
+
|
13
|
+
def each
|
14
|
+
@mutually_exclusive_outcome_odds.each {|outcome| yield outcome }
|
9
15
|
end
|
10
16
|
|
11
17
|
# the least likely of the odds to occur
|
12
18
|
# @return [FixedOdds] the least likely odd
|
13
19
|
def least_likely
|
14
|
-
|
20
|
+
min
|
15
21
|
end
|
16
22
|
|
17
23
|
# the most likely of the odds to occur
|
18
24
|
# @return [FixedOdds] the most likely odd
|
19
25
|
def most_likely
|
20
|
-
|
26
|
+
max
|
21
27
|
end
|
22
28
|
|
23
29
|
# the odds in ascending order of probability
|
24
30
|
# @return [Array<FixedOdds>] odds in ascending probability
|
25
31
|
def in_ascending_probability
|
26
|
-
|
32
|
+
sort
|
27
33
|
end
|
28
34
|
|
29
35
|
# the odds in descending order of probability
|
30
36
|
# @return [Array<FixedOdds>] odds in descending probability
|
31
37
|
def in_descending_probability
|
32
|
-
|
38
|
+
sort.reverse
|
33
39
|
end
|
34
40
|
|
35
41
|
# tells if arbitrage is possible for a collection of odds
|
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.1.
|
8
|
+
s.version = "3.1.1"
|
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 = "
|
12
|
+
s.date = "2013-08-15"
|
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 = [
|
@@ -4,138 +4,125 @@ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
4
4
|
describe "MutuallyExclusiveCollection" do
|
5
5
|
|
6
6
|
context "non-arbitrage methods" do
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
let(:good_team) { FixedOdds.from_s '-275' }
|
8
|
+
let(:draw) { FixedOdds.from_s '+429' }
|
9
|
+
let(:bad_team) { FixedOdds.from_s '+915' }
|
10
|
+
let(:events) { MutuallyExclusiveCollection.new [draw, bad_team, good_team] }
|
11
11
|
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
subject { @events }
|
12
|
+
subject { events }
|
16
13
|
|
17
|
-
its(:most_likely) { should ==
|
18
|
-
its(:least_likely) { should ==
|
19
|
-
its(:in_descending_probability) { should == [
|
20
|
-
its(:in_ascending_probability) { should == [
|
14
|
+
its(:most_likely) { should == good_team }
|
15
|
+
its(:least_likely) { should == bad_team }
|
16
|
+
its(:in_descending_probability) { should == [good_team, draw, bad_team] }
|
17
|
+
its(:in_ascending_probability) { should == [bad_team, draw, good_team] }
|
21
18
|
end
|
22
19
|
|
23
20
|
context "empty array" do
|
24
|
-
|
25
|
-
@bookmaker = MutuallyExclusiveCollection.new []
|
26
|
-
end
|
21
|
+
subject { MutuallyExclusiveCollection.new [] }
|
27
22
|
|
28
|
-
|
23
|
+
its(:bookmakers_return_rate) { should be_nil }
|
29
24
|
end
|
30
25
|
|
31
26
|
context "decimal odds arbitrage" do
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
@bookmaker2outcome2 = FixedOdds.from_s '3.85'
|
27
|
+
let(:bookmaker1outcome1) { FixedOdds.from_s '2.25' }
|
28
|
+
let(:bookmaker1outcome2) { FixedOdds.from_s '4.9' }
|
29
|
+
let(:bookmaker2outcome1) { FixedOdds.from_s '2.43' }
|
30
|
+
let(:bookmaker2outcome2) { FixedOdds.from_s '3.85' }
|
37
31
|
|
38
|
-
|
39
|
-
|
32
|
+
let(:bookmaker1) { MutuallyExclusiveCollection.new [bookmaker1outcome1, bookmaker1outcome2] }
|
33
|
+
let(:bookmaker2) { MutuallyExclusiveCollection.new [bookmaker2outcome1, bookmaker2outcome2] }
|
40
34
|
|
41
|
-
|
42
|
-
end
|
35
|
+
let(:bookmaker_vulnerable_to_arbitrage) { MutuallyExclusiveCollection.new [bookmaker2outcome1, bookmaker1outcome2] }
|
43
36
|
|
44
|
-
specify {
|
45
|
-
specify {
|
46
|
-
specify {
|
37
|
+
specify { bookmaker1.should_not be_arbitrageable }
|
38
|
+
specify { bookmaker2.should_not be_arbitrageable }
|
39
|
+
specify { bookmaker_vulnerable_to_arbitrage.should be_arbitrageable }
|
47
40
|
|
48
|
-
specify {
|
49
|
-
specify {
|
41
|
+
specify { bookmaker1.bookmakers_return_rate.should be_within(0.0001).of(0.0534) }
|
42
|
+
specify { bookmaker2.bookmakers_return_rate.should be_within(0.0001).of(0.0478) }
|
50
43
|
|
51
|
-
specify {
|
52
|
-
specify {
|
44
|
+
specify { bookmaker_vulnerable_to_arbitrage.profit_on_stake('£100'.to_money).should == '£4.63'.to_money }
|
45
|
+
specify { bookmaker_vulnerable_to_arbitrage.profit_percentage.should be_within(0.001).of(0.046) }
|
53
46
|
end
|
54
47
|
|
55
48
|
context "fractional odds arbitrage" do
|
56
|
-
|
57
|
-
|
58
|
-
@odds2 = FixedOdds.from_s '3/1'
|
49
|
+
let(:odds1) { FixedOdds.from_s '2/1' }
|
50
|
+
let(:odds2) { FixedOdds.from_s '3/1' }
|
59
51
|
|
60
|
-
|
61
|
-
end
|
52
|
+
let(:bookmaker_vulnerable_to_arbitrage) { MutuallyExclusiveCollection.new [odds1, odds2] }
|
62
53
|
|
63
|
-
subject {
|
54
|
+
subject { bookmaker_vulnerable_to_arbitrage }
|
64
55
|
|
65
|
-
specify {
|
56
|
+
specify { bookmaker_vulnerable_to_arbitrage.should be_arbitrageable }
|
66
57
|
its(:profit_percentage) { should be_within(0.001).of(0.2) }
|
67
|
-
specify {
|
58
|
+
specify { bookmaker_vulnerable_to_arbitrage.profit_on_stake('£500'.to_money).should == '£100'.to_money }
|
68
59
|
end
|
69
60
|
|
70
61
|
context "more than two mutually exclusive events" do
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
@odds3 = FixedOdds.from_s '18.0'
|
62
|
+
let(:odds1) { FixedOdds.from_s '2.3' }
|
63
|
+
let(:odds2) { FixedOdds.from_s '8.0' }
|
64
|
+
let(:odds3) { FixedOdds.from_s '18.0' }
|
75
65
|
|
76
|
-
|
77
|
-
end
|
66
|
+
let(:bookmaker_vulnerable_to_arbitrage) { MutuallyExclusiveCollection.new [odds1, odds2, odds3] }
|
78
67
|
|
79
|
-
subject {
|
68
|
+
subject { bookmaker_vulnerable_to_arbitrage }
|
80
69
|
|
81
|
-
|
70
|
+
it { should be_arbitrageable }
|
82
71
|
its(:profit_percentage) { should be_within(0.0000001).of(0.02996) }
|
83
72
|
|
84
73
|
describe "#percentages" do
|
85
74
|
it "gives the percentages to put on each bet" do
|
86
|
-
percentages =
|
75
|
+
percentages = bookmaker_vulnerable_to_arbitrage.percentages
|
87
76
|
percentages.should have(3).items
|
88
|
-
percentages[
|
89
|
-
percentages[
|
90
|
-
percentages[
|
77
|
+
percentages[odds1].should be_within(0.0001).of(0.7922)
|
78
|
+
percentages[odds2].should be_within(0.0001).of(0.1472)
|
79
|
+
percentages[odds3].should be_within(0.0001).of(0.0606)
|
91
80
|
end
|
92
81
|
end
|
93
82
|
|
94
83
|
describe "#stakes_for_total_stake" do
|
95
84
|
it "gives the right amounts" do
|
96
85
|
total = Money.parse '£500'
|
97
|
-
amounts =
|
86
|
+
amounts = bookmaker_vulnerable_to_arbitrage.stakes_for_total_stake total
|
98
87
|
amounts.should have(3).items
|
99
|
-
amounts[
|
100
|
-
amounts[
|
101
|
-
amounts[
|
88
|
+
amounts[odds1].should == '£396.14'.to_money
|
89
|
+
amounts[odds2].should == '£73.57'.to_money
|
90
|
+
amounts[odds3].should == '£30.29'.to_money
|
102
91
|
amounts.values.reduce(:+).should == total
|
103
92
|
end
|
104
93
|
end
|
105
94
|
|
106
|
-
specify {
|
95
|
+
specify { bookmaker_vulnerable_to_arbitrage.profit_on_stake('£500'.to_money).should == '£14.98'.to_money }
|
107
96
|
|
108
97
|
describe "#stakes_for_profit" do
|
109
98
|
it "gives the right amounts" do
|
110
|
-
amounts =
|
99
|
+
amounts = bookmaker_vulnerable_to_arbitrage.stakes_for_profit '£750'.to_money
|
111
100
|
amounts.should have(3).items
|
112
|
-
amounts[
|
113
|
-
amounts[
|
114
|
-
amounts[
|
101
|
+
amounts[odds1].should == '£19833.33'.to_money
|
102
|
+
amounts[odds2].should == '£3683.33'.to_money
|
103
|
+
amounts[odds3].should == '£1516.67'.to_money
|
115
104
|
amounts.values.reduce(:+).should == '£25033.33'.to_money
|
116
105
|
end
|
117
106
|
end
|
118
107
|
|
119
|
-
specify {
|
108
|
+
specify { bookmaker_vulnerable_to_arbitrage.stake_to_profit('£750'.to_money).should == '£25033.33'.to_money }
|
120
109
|
end
|
121
110
|
|
122
111
|
context "different events with same odds" do
|
123
|
-
|
124
|
-
|
125
|
-
@odds2 = FixedOdds.from_s '15/1'
|
112
|
+
let(:odds1) { FixedOdds.from_s '15/1' }
|
113
|
+
let(:odds2) { FixedOdds.from_s '15/1' }
|
126
114
|
|
127
|
-
|
128
|
-
end
|
115
|
+
let(:bookmaker_vulnerable_to_arbitrage) { MutuallyExclusiveCollection.new [odds1, odds2] }
|
129
116
|
|
130
|
-
specify {
|
131
|
-
specify {
|
117
|
+
specify { bookmaker_vulnerable_to_arbitrage.should be_arbitrageable }
|
118
|
+
specify { bookmaker_vulnerable_to_arbitrage.profit_on_stake('£100'.to_money).should == '£650'.to_money }
|
132
119
|
|
133
120
|
describe "#percentages" do
|
134
121
|
it "gives the percentages to put on each bet" do
|
135
|
-
percentages =
|
122
|
+
percentages = bookmaker_vulnerable_to_arbitrage.percentages
|
136
123
|
percentages.should have(2).items
|
137
|
-
percentages[
|
138
|
-
percentages[
|
124
|
+
percentages[odds1].should be_within(0.0001).of(0.5)
|
125
|
+
percentages[odds2].should be_within(0.0001).of(0.5)
|
139
126
|
end
|
140
127
|
end
|
141
128
|
end
|
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.1.
|
4
|
+
version: 3.1.1
|
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:
|
12
|
+
date: 2013-08-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: money
|
@@ -194,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
194
194
|
version: '0'
|
195
195
|
segments:
|
196
196
|
- 0
|
197
|
-
hash:
|
197
|
+
hash: -1412755025322987782
|
198
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
199
|
none: false
|
200
200
|
requirements:
|