em-betfair 0.2
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/.gitignore +3 -0
- data/Gemfile +8 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/em-betfair.gemspec +25 -0
- data/examples/scrape_betfair.rb +88 -0
- data/lib/em-betfair/betfair_client.rb +126 -0
- data/lib/em-betfair/response.rb +41 -0
- data/lib/em-betfair/response_parser.rb +135 -0
- data/lib/em-betfair/soap_renderer.rb +29 -0
- data/lib/em-betfair/views/exchange/get_all_markets.haml +23 -0
- data/lib/em-betfair/views/exchange/get_market.haml +12 -0
- data/lib/em-betfair/views/exchange/get_market_prices_compressed.haml +12 -0
- data/lib/em-betfair/views/exchange/get_market_traded_volume_compressed.haml +12 -0
- data/lib/em-betfair/views/global/get_all_event_types.haml +12 -0
- data/lib/em-betfair/views/global/login.haml +11 -0
- data/lib/em-betfair/views/global/retrieve_limb_message.haml +9 -0
- data/lib/em-betfair/views/global/submit_limb_message.haml +14 -0
- data/lib/em-betfair.rb +4 -0
- data/spec/functional/betfair_client_spec.rb +149 -0
- data/spec/remote/betfair_client_spec.rb +69 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/canned_responses/get_all_markets.xml +18 -0
- data/spec/support/canned_responses/get_all_markets_no_session.xml +18 -0
- data/spec/support/canned_responses/get_market.xml +3 -0
- data/spec/support/canned_responses/get_market_prices_compressed.xml +18 -0
- data/spec/support/canned_responses/get_market_traded_volume_compressed.xml +20 -0
- data/spec/support/canned_responses/login_failed.xml +19 -0
- data/spec/support/canned_responses/login_ok.xml +19 -0
- data/spec/support/canned_responses/soap_fault.xml +9 -0
- data/spec/unit/response_parser_spec.rb +45 -0
- metadata +143 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Betfair API client using Eventmachine and EM-Http
|
|
2
|
+
|
|
3
|
+
em-betfair is a work in progress evented client for the Betfair API. The following API calls have been implemented :
|
|
4
|
+
|
|
5
|
+
- login
|
|
6
|
+
- getMarket
|
|
7
|
+
- getAllMarkets
|
|
8
|
+
- getMarketPricesCompressed
|
|
9
|
+
- getMarketTradedVolumeCompressed
|
|
10
|
+
|
|
11
|
+
# Usage
|
|
12
|
+
|
|
13
|
+
gem install em-betfair
|
|
14
|
+
|
|
15
|
+
gem "em-betfair", "~> 0.1"
|
|
16
|
+
|
|
17
|
+
Create an instance of the client
|
|
18
|
+
|
|
19
|
+
config = {
|
|
20
|
+
"username" => "<YOUR BETFAIR USERNAME>",
|
|
21
|
+
"password" => "<YOUR BETFAIR PASSWORD>",
|
|
22
|
+
"product_id" => "<YOUR BETFAIR PRODUCTID>",
|
|
23
|
+
"exchange_endpoint" => "https://api.betfair.com/exchange/v5/BFExchangeService",
|
|
24
|
+
"global_endpoint" => "https://api.betfair.com/global/v3/BFGlobalService"
|
|
25
|
+
}
|
|
26
|
+
bf_client = Betfair::Client.new(config)
|
|
27
|
+
|
|
28
|
+
Making a call to the API:
|
|
29
|
+
|
|
30
|
+
EM::run {
|
|
31
|
+
bf_client.get_all_markets do |rsp|
|
|
32
|
+
|
|
33
|
+
rsp.raw_response # access the raw response body
|
|
34
|
+
rsp.parsed_response # access the Nokogiri XML object of the raw response
|
|
35
|
+
rsp.hash_response # access a hash of the response data
|
|
36
|
+
|
|
37
|
+
rsp.successfull # boolean for success
|
|
38
|
+
rsp.error # API error messge if not successfull
|
|
39
|
+
|
|
40
|
+
end
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Note, logging in to the API is handled internally by the client.
|
|
44
|
+
|
|
45
|
+
# Ruby versions
|
|
46
|
+
|
|
47
|
+
Tested on 1.9.2 but should work on 1.8.7 too.
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
data/em-betfair.gemspec
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = "em-betfair"
|
|
5
|
+
s.version = "0.2"
|
|
6
|
+
s.authors = ["George Sheppard"]
|
|
7
|
+
s.email = ["george@fuzzmonkey.co.uk"]
|
|
8
|
+
s.homepage = "https://github.com/fuzzmonkey/em-betfair"
|
|
9
|
+
s.summary = %q{Betfair API client using Eventmachine and EM-Http}
|
|
10
|
+
s.description = %q{em-betfair is a work in progress evented client for the Betfair API}
|
|
11
|
+
|
|
12
|
+
s.rubyforge_project = "em-betfair"
|
|
13
|
+
|
|
14
|
+
s.files = `git ls-files`.split("\n")
|
|
15
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
|
16
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
|
17
|
+
s.require_paths = ["lib"]
|
|
18
|
+
|
|
19
|
+
s.add_development_dependency "rspec"
|
|
20
|
+
s.add_runtime_dependency "eventmachine"
|
|
21
|
+
s.add_runtime_dependency "em-http-request"
|
|
22
|
+
s.add_runtime_dependency "haml"
|
|
23
|
+
s.add_runtime_dependency "nokogiri"
|
|
24
|
+
|
|
25
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Example script to scrape the Betfair API for prices and store them in redis as json strings.
|
|
2
|
+
|
|
3
|
+
# WARNING - Beware of the Betfair data usage limits!!!
|
|
4
|
+
|
|
5
|
+
require 'eventmachine'
|
|
6
|
+
require 'em-betfair'
|
|
7
|
+
require 'date'
|
|
8
|
+
require 'time'
|
|
9
|
+
require 'redis'
|
|
10
|
+
require 'json'
|
|
11
|
+
|
|
12
|
+
# Not requiring activesupport just to get ordanalize
|
|
13
|
+
class Fixnum
|
|
14
|
+
def ordinalize
|
|
15
|
+
if (11..13).include?(self % 100)
|
|
16
|
+
"#{self}th"
|
|
17
|
+
else
|
|
18
|
+
case self % 10
|
|
19
|
+
when 1; "#{self}st"
|
|
20
|
+
when 2; "#{self}nd"
|
|
21
|
+
when 3; "#{self}rd"
|
|
22
|
+
else "#{self}th"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
EM::run {
|
|
29
|
+
|
|
30
|
+
config = {
|
|
31
|
+
"username" => "<YOUR BETFAIR USERNAME>",
|
|
32
|
+
"password" => "<YOUR BETFAIR PASSWORD>",
|
|
33
|
+
"product_id" => "<YOUR BETFAIR PRODUCTID>",
|
|
34
|
+
"exchange_endpoint" => "https://api.betfair.com/exchange/v5/BFExchangeService",
|
|
35
|
+
"global_endpoint" => "https://api.betfair.com/global/v3/BFGlobalService"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
bf_client = Betfair::Client.new(config)
|
|
39
|
+
redis_client = Redis.new
|
|
40
|
+
timers = {}
|
|
41
|
+
num_timers = 0
|
|
42
|
+
|
|
43
|
+
# Fetch all the horse racing markets in the UK for today
|
|
44
|
+
bf_client.get_all_markets ["GBR"], [7], "#{Date.today} 00:00:00", "#{Date.today} 23:59:00" do |response|
|
|
45
|
+
|
|
46
|
+
response.hash_response["market_data"].each do |market_id,market_hash|
|
|
47
|
+
|
|
48
|
+
menu_path = market_hash["menu_path"]
|
|
49
|
+
track_name = menu_path.match(/\\.+\\.+\\(\w+\s{0,1}\w*)/)[1]
|
|
50
|
+
|
|
51
|
+
# Lets just fetch the 'real' markets rather than AvB, Antepost, Place etc
|
|
52
|
+
next unless track_name && !market_hash["name"].match(/To Be Placed/) && !menu_path.match(/ANTEPOST/) && !menu_path.match(/Forecast/) && track_name.match(/#{Date.today.day.ordinalize}/)
|
|
53
|
+
|
|
54
|
+
# Fetch the runners
|
|
55
|
+
bf_client.get_market market_id do |market_response|
|
|
56
|
+
market_details = market_response.hash_response
|
|
57
|
+
puts "Got market #{market_id} #{track_name} #{market_hash["name"]} #{Time.parse(market_details["market_time"])}"
|
|
58
|
+
runners = market_details["runners"]
|
|
59
|
+
|
|
60
|
+
# If the market is active, setup some periodical timers
|
|
61
|
+
next unless market_details["status"] == "ACTIVE"
|
|
62
|
+
|
|
63
|
+
# would be nice to get the data from getSilks here
|
|
64
|
+
redis_client.hset track_name, market_hash["name"], {"market_details" => market_details, "prices" => nil}.to_json
|
|
65
|
+
|
|
66
|
+
next if num_timers > 59 # free API restriction
|
|
67
|
+
|
|
68
|
+
# Update the odds every 60 seconds
|
|
69
|
+
timers[market_id] = EventMachine::PeriodicTimer.new(60) do
|
|
70
|
+
num_timers += 1
|
|
71
|
+
bf_client.get_market_prices_compressed market_id do |prices_response|
|
|
72
|
+
puts "Fetching prices for #{market_id}"
|
|
73
|
+
price_data = prices_response.hash_response
|
|
74
|
+
timers[market_id].cancel if timers[market_id] && price_data["status"] != "ACTIVE"
|
|
75
|
+
race = redis_client.hget track_name, market_hash["name"]
|
|
76
|
+
race_hash = JSON.parse(race)
|
|
77
|
+
race_hash["prices"] = price_data
|
|
78
|
+
redis_client.hset track_name, market_hash["name"], race_hash.to_json
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
require 'uri'
|
|
2
|
+
require 'em-http'
|
|
3
|
+
require 'nokogiri'
|
|
4
|
+
#require 'tzinfo'
|
|
5
|
+
|
|
6
|
+
module Betfair
|
|
7
|
+
|
|
8
|
+
BETFAIR_TIME_ZONES = {"RSA"=>"Africa/Johannesburg", "AST"=>"US/Arizona", "MST"=>"US/Mountain", "JPT"=>"Japan", "HK"=>"Hongkong", "GMT"=>"GMT", "PKT"=>"Etc/GMT-5", "UAE"=>"Asia/Dubai", "CST"=>"US/Central", "AKST"=>"US/Alaska", "BRT"=>"Brazil/East", "INT"=>"Asia/Calcutta", "SMT"=>"America/Santiago", "MSK"=>"Europe/Moscow", "AWST"=>"Australia/Perth", "PST"=>"US/Pacific", "EST"=>"US/Eastern", "KMT"=>"Jamaica", "CET"=>"CET", "ANST"=>"Australia/Darwin", "ACST"=>"Australia/Adelaide", "NZT"=>"NZ", "UKT"=>"Europe/London", "AMT"=>"Brazil/West", "THAI"=>"Asia/Bangkok", "SJMT"=>"America/Costa_Rica", "HST"=>"US/Hawaii", "EET"=>"EET", "AEST"=>"Australia/Sydney", "IEST"=>"America/Indiana/Indianapolis", "AQST"=>"Australia/Queensland"}
|
|
9
|
+
|
|
10
|
+
class Client
|
|
11
|
+
|
|
12
|
+
attr_accessor :session_token
|
|
13
|
+
|
|
14
|
+
# config - hash of betfair credentials & API endpoints
|
|
15
|
+
# { "username" => "<YOUR BETFAIR USERNAME>", "password" => "<YOUR BETFAIR PASSWORD>", "product_id" => "<YOUR BETFAIR PRODUCTID>", "exchange_endpoint" => "https://api.betfair.com/exchange/v5/BFExchangeService", "global_endpoint" => "https://api.betfair.com/global/v3/BFGlobalService" }
|
|
16
|
+
def initialize config
|
|
17
|
+
@config = config
|
|
18
|
+
@session_token = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Creates a session on the Betfair API, used by Betfair::Client internally to maintain session.
|
|
22
|
+
def login &block
|
|
23
|
+
build_request "global", "login", {"username" => @config["username"], "password" => @config["password"], "product_id" => @config["product_id"]}, block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns all the available markets on Betfair.
|
|
27
|
+
#
|
|
28
|
+
# countries - array of ISO 3166-1 country codes
|
|
29
|
+
# event_type_ids - array of Betfair event ids
|
|
30
|
+
# to_date - DateTime
|
|
31
|
+
# from_date - DateTime
|
|
32
|
+
def get_all_markets countries=nil, event_type_ids=nil, to_date=nil, from_date=nil, &block
|
|
33
|
+
with_session do
|
|
34
|
+
build_request "exchange", "get_all_markets", {"countries" => countries, "event_type_ids" => event_type_ids, "to_date" => to_date, "from_date" => from_date}, block
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the details of a specifc market
|
|
39
|
+
#
|
|
40
|
+
# market_id - Betfair market ID
|
|
41
|
+
def get_market market_id, &block
|
|
42
|
+
with_session do
|
|
43
|
+
build_request "exchange", "get_market", {"market_id" => market_id }, block
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the compressed market prices
|
|
48
|
+
#
|
|
49
|
+
# market_id - Betfair market ID
|
|
50
|
+
# currency_code - three letter ISO 4217 country code
|
|
51
|
+
def get_market_prices_compressed market_id, currency_code=nil, &block
|
|
52
|
+
with_session do
|
|
53
|
+
build_request "exchange", "get_market_prices_compressed", {"market_id" => market_id, "currency_code" => currency_code}, block
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the compressed traded volumes
|
|
58
|
+
#
|
|
59
|
+
# market_id - Betfair market ID
|
|
60
|
+
# currency_code - three letter ISO 4217 country code
|
|
61
|
+
def get_market_traded_volume_compressed market_id, currency_code=nil, &block
|
|
62
|
+
with_session do
|
|
63
|
+
build_request "exchange", "get_market_traded_volume_compressed", {"market_id" => market_id, "currency_code" => currency_code}, block
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
# Builds the EM::Http request object
|
|
70
|
+
#
|
|
71
|
+
# service_name - the endpoint to use (exchange or global)
|
|
72
|
+
# action - the API method to call on the API
|
|
73
|
+
# data - hash of parameters to populate the SOAP request
|
|
74
|
+
# block - the ballback for this request
|
|
75
|
+
def build_request service_name, action, data={}, block
|
|
76
|
+
request_data = { :data => data.merge!({"session_token" => @session_token}) }
|
|
77
|
+
soap_req = Betfair::SOAPRenderer.new( service_name, action ).render( request_data )
|
|
78
|
+
url = get_endpoint service_name
|
|
79
|
+
headers = { 'SOAPAction' => action, 'Accept-Encoding' => 'gzip,deflate', 'Content-type' => 'text/xml;charset=UTF-8' }
|
|
80
|
+
req = EventMachine::HttpRequest.new(url).post :body => soap_req, :head => headers
|
|
81
|
+
req.errback { block.call(Response.new(nil,nil,false,"Error connecting to the API")) }
|
|
82
|
+
req.callback { parse_response(req.response,block) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Parses the API response, building a response object
|
|
86
|
+
#
|
|
87
|
+
# raw_rsp - response body from EM:Http request
|
|
88
|
+
# block - callback for this request
|
|
89
|
+
def parse_response raw_rsp, block
|
|
90
|
+
parsed_response = Nokogiri::XML raw_rsp
|
|
91
|
+
|
|
92
|
+
soap_fault = parsed_response.xpath("//faultstring").first
|
|
93
|
+
if soap_fault
|
|
94
|
+
block.call(Response.new(raw_rsp,parsed_response,false,soap_fault.text))
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
api_error = parsed_response.xpath("//header/errorCode").text
|
|
99
|
+
method_error = parsed_response.xpath("//errorCode").last.text
|
|
100
|
+
|
|
101
|
+
error_rsp = api_error == "OK" ? method_error : api_error
|
|
102
|
+
unless api_error == "OK" && method_error == "OK"
|
|
103
|
+
@session_token = nil if [api_error,method_error].include?("NO_SESSION") # so we try and login on the next request
|
|
104
|
+
block.call(Response.new(raw_rsp,parsed_response,false,error_rsp))
|
|
105
|
+
return
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
@session_token = parsed_response.xpath("//sessionToken").text
|
|
109
|
+
|
|
110
|
+
block.call Response.new(raw_rsp,parsed_response,true)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def with_session
|
|
114
|
+
yield unless @session_token.nil?
|
|
115
|
+
login do |response|
|
|
116
|
+
yield
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def get_endpoint service_name
|
|
121
|
+
return @config["#{service_name}_endpoint"]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Stolen from Rails 3
|
|
2
|
+
def underscore(camel_cased_word)
|
|
3
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
|
4
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
|
5
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
|
6
|
+
tr("-", "_").
|
|
7
|
+
downcase
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module Betfair
|
|
11
|
+
|
|
12
|
+
class Response
|
|
13
|
+
include ResponseParser
|
|
14
|
+
|
|
15
|
+
attr_accessor :raw_response # response string
|
|
16
|
+
attr_accessor :parsed_response # response xml
|
|
17
|
+
attr_accessor :successfull
|
|
18
|
+
attr_accessor :error
|
|
19
|
+
|
|
20
|
+
def initialize raw, parsed, successfull, error=""
|
|
21
|
+
@raw_response = raw
|
|
22
|
+
@parsed_response = parsed
|
|
23
|
+
@successfull = successfull
|
|
24
|
+
@error = error
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# lazy loaded
|
|
28
|
+
def hash_response
|
|
29
|
+
method = get_response_type
|
|
30
|
+
self.send method.to_sym, self.parsed_response if method && self.respond_to?(method)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def get_response_type
|
|
34
|
+
return nil unless self.parsed_response.respond_to?(:xpath)
|
|
35
|
+
response_type = self.parsed_response.xpath("//ns:Envelope/ns:Body", "ns" => "http://schemas.xmlsoap.org/soap/envelope/").first.elements.first.name
|
|
36
|
+
underscore(response_type.gsub("Response",""))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
module Betfair
|
|
2
|
+
|
|
3
|
+
# TODO - version this to handle changes in the API
|
|
4
|
+
# TODO - this might be nicer as a structs style setup. e.g
|
|
5
|
+
# markets_rsp = GetAllMarkets.new(parsed_response)
|
|
6
|
+
module ResponseParser
|
|
7
|
+
|
|
8
|
+
# TODO - handle timezones, return local & utc time
|
|
9
|
+
# tz = TZInfo::Timezone.get(new_time_zone)
|
|
10
|
+
# race_time = tz.utc_to_local(race_time)
|
|
11
|
+
|
|
12
|
+
# TODO - return values as proper types rather than strings
|
|
13
|
+
|
|
14
|
+
def login xml
|
|
15
|
+
{"currency" => xml.xpath("//currency").text}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get_all_markets xml
|
|
19
|
+
market_data = xml.xpath("//marketData").text
|
|
20
|
+
all_markets_hash = {"market_data" => {}}
|
|
21
|
+
market_data.split(":").each do |market|
|
|
22
|
+
market_fields = market.split("~")
|
|
23
|
+
next unless market_fields.size >= 16 #incase they append more fields
|
|
24
|
+
market_hash = {}
|
|
25
|
+
market_hash["id"] = market_fields[0]
|
|
26
|
+
market_hash["name"] = market_fields[1]
|
|
27
|
+
market_hash["type"] = market_fields[2]
|
|
28
|
+
market_hash["status"] = market_fields[3]
|
|
29
|
+
market_hash["date"] = Time.at(market_fields[4].to_i/1000).utc #Epoc time
|
|
30
|
+
market_hash["menu_path"] = market_fields[5]
|
|
31
|
+
market_hash["event_hierarchy"] = market_fields[6]
|
|
32
|
+
market_hash["bet_delay"] = market_fields[7]
|
|
33
|
+
market_hash["exchange_id"] = market_fields[8]
|
|
34
|
+
market_hash["country_code"] = market_fields[9]
|
|
35
|
+
market_hash["last_refresh"] = Time.at(market_fields[10].to_i/1000).utc #Epoc time
|
|
36
|
+
market_hash["num_runners"] = market_fields[11]
|
|
37
|
+
market_hash["num_winners"] = market_fields[12]
|
|
38
|
+
market_hash["total_amount_matched"] = market_fields[13]
|
|
39
|
+
market_hash["bsp_market"] = market_fields[14] == "Y"
|
|
40
|
+
market_hash["turning_in_play"] = market_fields[15] == "Y"
|
|
41
|
+
|
|
42
|
+
all_markets_hash["market_data"][market_fields[0]] = market_hash
|
|
43
|
+
end
|
|
44
|
+
all_markets_hash
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_market xml
|
|
48
|
+
market_hash = {}
|
|
49
|
+
market_hash["id"] = xml.xpath("//market/marketId").text
|
|
50
|
+
market_hash["status"] = xml.xpath("//market/marketStatus").text
|
|
51
|
+
market_hash["parent_id"] = xml.xpath("//market/parentEventId").text
|
|
52
|
+
market_hash["country_code"] = xml.xpath("//market/countryISO3").text
|
|
53
|
+
market_hash["event_type"] = xml.xpath("//market/eventTypeId").text
|
|
54
|
+
market_hash["base_rate"] = xml.xpath("//market/marketBaseRate").text
|
|
55
|
+
market_hash["market_name"] = xml.xpath("//market/name").text
|
|
56
|
+
market_hash["num_winners"] = xml.xpath("//market/numberOfWinners").text
|
|
57
|
+
market_hash["market_time"] = xml.xpath("//market/marketTime").text
|
|
58
|
+
|
|
59
|
+
market_hash["runners"] = []
|
|
60
|
+
|
|
61
|
+
xml.xpath("//runners").children.each do |xml_runner|
|
|
62
|
+
name = xml_runner.xpath("name").text
|
|
63
|
+
selection_id = xml_runner.xpath("selectionId").text
|
|
64
|
+
market_hash["runners"].push({"selection_id" => selection_id, "name" => name})
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
market_hash
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def get_market_prices_compressed xml
|
|
71
|
+
prices_hash = {}
|
|
72
|
+
prices = xml.xpath("//marketPrices").text
|
|
73
|
+
# Betfair uses colons as a seperator and escaped colons as a different seperator, grr.
|
|
74
|
+
# [1..-1] removes the first empty string
|
|
75
|
+
prices_data = prices.gsub('\:', 'ECSCOLON')[1..-1].split(":")
|
|
76
|
+
|
|
77
|
+
header_data = prices_data.slice!(0).gsub("ECSCOLON",":").split("~")
|
|
78
|
+
|
|
79
|
+
# TODO - parse removed runners properly
|
|
80
|
+
["market_id","currency","status","in_play_delay","num_winners","market_info","discount_allowed","market_base_rate","refresh_time","removed_runners","bsp_market"].each_with_index do |field,index|
|
|
81
|
+
prices_hash[field] = header_data.at(index)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
prices_data.each do |runner|
|
|
85
|
+
runner_hash = {}
|
|
86
|
+
runner_info, lay_prices, back_prices = runner.split("|")
|
|
87
|
+
runner_data = runner_info.split("~")
|
|
88
|
+
|
|
89
|
+
["selection_id","order_index","total_matched","last_price_matched","handicap","reduction_factor","vacant","asian_line_id","far_sp_price","near_sp_price","actual_sp_price"].each_with_index do |field,index|
|
|
90
|
+
runner_hash[field] = runner_data.at(index)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
runner_hash["lay_prices"] = []
|
|
94
|
+
lay_prices.split("~").each_slice(4) do |prices|
|
|
95
|
+
runner_hash["lay_prices"].push({"odds" => prices[0], "amount" => prices[1], "type" => prices[2], "depth" => prices[3]})
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
runner_hash["back_prices"] = []
|
|
99
|
+
back_prices.split("~").each_slice(4) do |prices|
|
|
100
|
+
runner_hash["back_prices"].push({"odds" => prices[0], "amount" => prices[1], "type" => prices[2], "depth" => prices[3]})
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
prices_hash[runner_hash["selection_id"]] = runner_hash
|
|
104
|
+
end
|
|
105
|
+
prices_hash
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def get_market_traded_volume_compressed xml
|
|
109
|
+
traded_volumne_hash = {}
|
|
110
|
+
traded = xml.xpath("//tradedVolume").text
|
|
111
|
+
market_id = xml.xpath("//marketId").text
|
|
112
|
+
currency = xml.xpath("//currencyCode").text
|
|
113
|
+
# Betfair uses colons as a seperator and escaped colons as a different seperator, grr.
|
|
114
|
+
# [1..-1] removes the first empty string
|
|
115
|
+
traded_data = traded.gsub(/\\:/, "ECSCOLON")[1..-1].split(":")
|
|
116
|
+
traded_data.each do |runner|
|
|
117
|
+
# TODO - replace ECSCOLON with :
|
|
118
|
+
runner_hash = {"traded_amounts" => []}
|
|
119
|
+
runner_data = runner.split("|")
|
|
120
|
+
header_data = runner_data.slice!(0).split("~")
|
|
121
|
+
["selection_id", "asian_line_id", "bsp", "total_bsp_back_matched", "total_bsp_liability_matched"].each_with_index do |field,index|
|
|
122
|
+
runner_hash[field] = header_data.at(index)
|
|
123
|
+
end
|
|
124
|
+
runner_data.each do |traded_amount|
|
|
125
|
+
odds, total_matched = traded_amount.split("~")
|
|
126
|
+
runner_hash["traded_amounts"].push({"odds" => odds, "total_matched" => total_matched})
|
|
127
|
+
end
|
|
128
|
+
traded_volumne_hash[runner_hash["selection_id"]] = runner_hash
|
|
129
|
+
end
|
|
130
|
+
traded_volumne_hash
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'haml'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
|
|
4
|
+
module Betfair
|
|
5
|
+
|
|
6
|
+
# Utility class to render a SOAP request from a haml file, embedding data
|
|
7
|
+
# elements as necessary
|
|
8
|
+
class SOAPRenderer
|
|
9
|
+
|
|
10
|
+
def initialize service, soap_name
|
|
11
|
+
base = Pathname.new(__FILE__).realpath.parent
|
|
12
|
+
file = "#{base}/views/#{service}/#{soap_name}.haml"
|
|
13
|
+
unless File.exists?( file )
|
|
14
|
+
$log.error "Cannot find HAML: #{file}" unless $log.nil?
|
|
15
|
+
raise "Cannot find HAML: #{file}"
|
|
16
|
+
end
|
|
17
|
+
@engine = Haml::Engine.new( File.read( file ) ) # this is quite expensive, might be better to keep a hash of renderers
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render content
|
|
21
|
+
content.each do |key,value|
|
|
22
|
+
self.instance_variable_set( "@#{key}", value )
|
|
23
|
+
end
|
|
24
|
+
@engine.render( self )
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
%soapenv:Envelope{ "xmlns:soapenv"=> "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:bfex"=>"http://www.betfair.com/publicapi/v5/BFExchangeService/", "xmlns:v5" => 'http://www.betfair.com/publicapi/types/exchange/v5/' }
|
|
2
|
+
%soapenv:Header
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfex:getAllMarkets
|
|
5
|
+
%bfex:request
|
|
6
|
+
%header
|
|
7
|
+
- if @data["client_stamp"]
|
|
8
|
+
%clientStamp= @data["client_stamp"]
|
|
9
|
+
%sessionToken= @data["session_token"]
|
|
10
|
+
- if @data["locale"]
|
|
11
|
+
%locale= @data["locale"]
|
|
12
|
+
- if @data["event_type_ids"]
|
|
13
|
+
%eventTypeIds
|
|
14
|
+
- @data["event_type_ids"].each do |event_id|
|
|
15
|
+
%int=event_id
|
|
16
|
+
- if @data["countries"]
|
|
17
|
+
%countries
|
|
18
|
+
- @data["countries"].each do |cc|
|
|
19
|
+
%Country= cc
|
|
20
|
+
- if @data["from_date"]
|
|
21
|
+
%fromDate= @data["from_date"]
|
|
22
|
+
- if @data["to_date"]
|
|
23
|
+
%toDate= @data["to_date"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
%soapenv:Envelope{"xmlns:soapenv" => "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:bfex" => "http://www.betfair.com/publicapi/v5/BFExchangeService/" }
|
|
2
|
+
%soapenv:Header
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfex:getMarket
|
|
5
|
+
%bfex:request
|
|
6
|
+
%header
|
|
7
|
+
- if @data["client_stamp"]
|
|
8
|
+
%clientStamp= @data["client_stamp"]
|
|
9
|
+
%sessionToken= @data["session_token"]
|
|
10
|
+
- if @data["locale"]
|
|
11
|
+
%locale= @data["locale"]
|
|
12
|
+
%marketId= @data["market_id"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
%Envelope{ "xmlns:bfex" => 'http://www.betfair.com/publicapi/v5/BFExchangeService/', "xmlns:soapenv" => 'http://schemas.xmlsoap.org/soap/envelope/'}
|
|
2
|
+
%soapenv:Header
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfex:getMarketPricesCompressed
|
|
5
|
+
%bfex:request
|
|
6
|
+
%header
|
|
7
|
+
- if @data["client_stamp"]
|
|
8
|
+
%clientStamp= @data["client_stamp"]
|
|
9
|
+
%sessionToken= @data["session_token"]
|
|
10
|
+
- if @data["currency_code"]
|
|
11
|
+
%currencyCode= @data["currency_code"]
|
|
12
|
+
%marketId= @data["market_id"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
%Envelope{ "xmlns:bfex" => 'http://www.betfair.com/publicapi/v5/BFExchangeService/', "xmlns:soapenv" => 'http://schemas.xmlsoap.org/soap/envelope/'}
|
|
2
|
+
%soapenv:Header
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfex:getMarketTradedVolumeCompressed
|
|
5
|
+
%bfex:request
|
|
6
|
+
%header
|
|
7
|
+
- if @data["client_stamp"]
|
|
8
|
+
%clientStamp= @data["client_stamp"]
|
|
9
|
+
%sessionToken= @data["session_token"]
|
|
10
|
+
- if @data["currency_code"]
|
|
11
|
+
%currencyCode= @data["currency_code"]
|
|
12
|
+
%marketId= @data["market_id"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
%soapenv:Envelope{"xmlns:soapenv"=>"http://schemas.xmlsoap.org/soap/envelope/", "xmlns:bfg"=> "http://www.betfair.com/publicapi/v3/BFGlobalService/" }
|
|
2
|
+
%soapenv:Header/
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfg:getAllEventTypes
|
|
5
|
+
%bfg:request
|
|
6
|
+
%header
|
|
7
|
+
- if @data["client_stamp"]
|
|
8
|
+
%clientStamp= @data["client_stamp"]
|
|
9
|
+
%sessionToken= @data["session_token"]
|
|
10
|
+
- if @data["locale"]
|
|
11
|
+
%locale= @data["locale"]
|
|
12
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
%soapenv:Envelope{ "xmlns:soapenv"=> "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:bfg"=> "http://www.betfair.com/publicapi/v3/BFGlobalService/" }
|
|
2
|
+
%soapenv:Header
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfg:login
|
|
5
|
+
%bfg:request
|
|
6
|
+
%ipAddress= @data['ip_address']||0
|
|
7
|
+
%locationId= @data['location_id']||0
|
|
8
|
+
%password= @data['password']
|
|
9
|
+
%productId= @data['product_id']
|
|
10
|
+
%username= @data['username']
|
|
11
|
+
%vendorSoftwareId= @data['vender_software_id']||0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
%soapenv:Envelope{ "xmlns:soapenv"=> "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:bfg"=> "http://www.betfair.com/publicapi/v3/BFGlobalService/" }
|
|
2
|
+
%soapenv:Header
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfg:retrieveLIMBMessage
|
|
5
|
+
%bfg:request
|
|
6
|
+
%header
|
|
7
|
+
- if @data["client_stamp"]
|
|
8
|
+
%clientStamp= @data["client_stamp"]
|
|
9
|
+
%sessionToken= @data["session_token"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
%soapenv:Envelope{ "xmlns:soapenv"=> "http://schemas.xmlsoap.org/soap/envelope/", "xmlns:bfg"=> "http://www.betfair.com/publicapi/v3/BFGlobalService/", "xmlns:v3" => "http://www.betfair.com/publicapi/types/global/v3/" }
|
|
2
|
+
%soapenv:Header
|
|
3
|
+
%soapenv:Body
|
|
4
|
+
%bfg:submitLIMBMessage>
|
|
5
|
+
%bfg:request
|
|
6
|
+
%header
|
|
7
|
+
- if @data["client_stamp"]
|
|
8
|
+
%clientStamp= @data["client_stamp"]
|
|
9
|
+
%sessionToken= @data["session_token"]
|
|
10
|
+
%password= @data["password"]
|
|
11
|
+
%submitPasswordChangeMessage
|
|
12
|
+
%messageId= @data["message_id"]
|
|
13
|
+
%newPassword= @data["new_password"]
|
|
14
|
+
%newPasswordRepeat= @data["new_password"]
|
data/lib/em-betfair.rb
ADDED