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