em-betfair 0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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