2022-11-12 02:27:46 +01:00

174 lines
7.5 KiB
Ruby

module Integrations
module Betfair
class BetManager < Base
def check_qualified_bet_outcome(bets)
bets_lookup = {}
market_ids = []
bets_by_bet_ids = []
reconciliation_limit = 20
bet_count = 0
bets.each do |bet|
bet_count += 1
next unless bet.exchange_market_details['market_id'] && bet.exchange_market_details['selection_id']
market_ids << bet.exchange_market_details['market_id']
bets_lookup[bet.exchange_market_details['market_id'].to_s] = { selection_id: bet.exchange_market_details['selection_id'], bet_id: bet.id }
bets_by_bet_ids << bet unless bet.exchange_bet_id.blank?
next unless (reconciliation_limit == market_ids.size) || (bet_count == bets.count)
reconcile_bets_by_markets(market_ids, bets_lookup) unless market_ids.empty?
market_ids = []
bets_lookup = {}
end
reconcile_bets_by_bet_ids(bets_by_bet_ids) unless bets_by_bet_ids.empty?
end
def reconcile_bets_by_markets(market_ids, bets_lookup)
outcomes = { LOSER: [], WINNER: [] }
body = { marketIds: market_ids }
markets = self.class.post("#{API_BETTING_ENDPOINT}/listMarketBook/", { headers: @connection.api_headers, body: body.to_json })
markets.each do |market|
next unless market['status'] == 'CLOSED'
bet = bets_lookup[market['marketId']]
next unless bet
runners = market['runners']
runners.each do |runner|
next unless runner['selectionId'] == bet[:selection_id]
x_status = runner['status'].to_sym
if outcomes[x_status]
outcomes[x_status] << bet[:bet_id]
end
break
end
end
ActiveRecord::Base.connection.execute("update bets set outcome = 'lost', outcome_value=stake where #{ActiveRecord::Base.sanitize_sql(['id in (?)', outcomes[:LOSER]])}") if outcomes[:LOSER].size.positive?
ActiveRecord::Base.connection.execute("update bets set outcome = 'won', outcome_value=expected_value where #{ActiveRecord::Base.sanitize_sql(['id in (?)', outcomes[:WINNER]])}") if outcomes[:WINNER].size.positive?
end
def reconcile_bets_by_bet_ids(bets)
return unless bets.count.positive?
results = {}
open_bet_ids = bets.pluck(:exchange_bet_id)
%w[SETTLED VOIDED CANCELLED].each do |status|
body = { betStatus: status, betIds: open_bet_ids }
r = self.class.post("#{API_BETTING_ENDPOINT}/listClearedOrders/", { headers: @connection.api_headers, body: body.to_json })
orders = r['clearedOrders']
if status == 'SETTLED'
# the status updates are by betOutcome
orders.each do |o|
results[o['betOutcome']] ||= []
results[o['betOutcome']] << o['betId']
end
else
results[status] ||= []
bet_ids = orders.map { |o| o['betId'] }
results[status] = bet_ids
end
end
results.keys.each do |k|
next if results[k].blank?
Bet.unscoped.open.where(exchange_bet_id: results[k]).update(outcome: k.downcase)
end
end
def place_bet(bet, stake)
body = { marketId: bet.exchange_market_details['market_id'], customerRef: bet.tip_provider_bet_id }
body[:instructions] =
[{ orderType: 'LIMIT', side: 'BACK', selectionId: bet.exchange_market_details['selection_id'].to_s,
limitOrder: { timeInForce: 'FILL_OR_KILL', size: stake.to_s, price: bet.tip_provider_odds.to_s, persistenceType: 'LAPSE' } }]
r = self.class.post("#{API_BETTING_ENDPOINT}/placeOrders/", { headers: @connection.api_headers, body: body.to_json })
success = r['status'] == 'SUCCESS'
if success
bet_id = r['instructionReports'][0]['betId']
return bet_id
end
error_code = r['errorCode']
raise "[Place bet] Placing bet failed: #{error_code}"
end
def bet_event(bet, update_bet = false)
# return the event for this bet.
# easy if we have the event_id, else we have to search.
event_id = []
event_id << bet.exchange_event_id unless bet.exchange_event_id.blank?
events = list_events({ eventIds: event_id, textQuery: bet.exchange_event_name })
e_id = events[0]['event']['id'] if events.length.positive?
if e_id
bet.update(exchange_event_id: e_id) if update_bet
return e_id
end
raise '[bet_event] Error getting event id'
end
def bet_odds(bet)
event_market_selection_hash = event_market_selection(bet)
raise '[bet odds] - market not available' unless event_market_selection_hash['market_id']
raise '[bet odds] - selection not available' unless event_market_selection_hash['selection_id']
body = { marketId: event_market_selection_hash['market_id'], selectionId: event_market_selection_hash['selection_id'] }
body[:priceProjection] = { priceData: ['EX_ALL_OFFERS'], virtualise: true }
r = self.class.post("#{API_BETTING_ENDPOINT}/listRunnerBook/", { headers: @connection.api_headers, body: body.to_json })
runners = r[0]['runners']
raise '[Bet odds] - cannot identify prices' unless runners
rs = runners.first
raise '[Bet odds] - cannot identify prices' unless rs && rs['ex'] && rs['ex']['availableToBack']
prices = []
stakes = {}
rs['ex']['availableToBack'].each do |ex|
prices << ex['price']
stakes[(ex['price']).to_s] = ex['size']
end
{ prices: prices, stakes: stakes, liquidity: r[0]["totalAvailable"] }
end
def event_market_selection(bet)
if bet.exchange_event_id.blank?
bet_event(bet, true)
raise '[No event id]' if bet.exchange_event_id.blank?
end
body = { maxResults: 500, filter: { eventIds: [bet.exchange_event_id] }, marketProjection: %w[MARKET_DESCRIPTION RUNNER_DESCRIPTION RUNNER_METADATA] }
markets = self.class.post("#{API_BETTING_ENDPOINT}/listMarketCatalogue/", { headers: @connection.api_headers, body: body.to_json })
m_details = bet.exchange_market_details
returned_markets = []
returned_selections = []
markets.each do |market|
returned_markets << market['marketName']
next unless m_details['market'].casecmp(market['marketName']).zero?
m_details['market_id'] = market['marketId']
fuzzy_match_runners = market['marketName'] == 'Match Odds'
runners = market['runners']
m_selection = m_details['selection']
runners.each do |runner|
returned_selections << runner['runnerName']
runner_name_matched = runner['runnerName'].casecmp(m_selection).zero?
if !runner_name_matched && fuzzy_match_runners
runner_name_matched = (runner['runnerName'].downcase.split & m_selection.downcase.split).length.positive?
end
next unless runner_name_matched
m_details['selection_id'] = runner['selectionId']
m_details['selection'] = runner['runnerName']
break
end
end
m_details['returned_markets'] = returned_markets unless m_details.key?('market_id')
m_details['returned_selections'] = returned_selections unless m_details.key?('selection_id')
bet.update(exchange_market_details: m_details)
m_details
end
end
end
end