class BetPlacementService < ApplicationJob queue_as :high def perform(args = {}) # do not proceed if we have already procssed a bet for this tip bet = args[:bet] # get a valid betfair instance for the account we are using, halt if not valid. exchange = Integrations::Betfair::BetManager.new(bet.exchange_account) return unless exchange last_step = 'Checking event id' begin # check this bet with the exchange and populate the event id if it is missing unless bet.exchange_event_id bet.exchange_event_id = exchange.bet_event(bet) end bet.placement_attempts += 1 # check the odds are still good at the exchange for that event last_step = 'Checking odds' prices_and_stakes = exchange.bet_odds(bet) prices = prices_and_stakes[:prices] bet.exchange_odds = prices_and_stakes bet.outcome = 'expired' tip_odds = bet.tip_provider_odds.to_f # use tip odds or get closest match to tip odds offered by the exchange last_step = 'Determining if odds optimal' margin = tip_odds * bet.exchange_account.current_stake_strategy[:odds_margin] max_tip_odds = tip_odds + margin odds_to_execute_at = prices.min_by { |num| (max_tip_odds - num).abs } raise 'Cannot determine optimal odds' unless odds_to_execute_at if odds_to_execute_at.between?(tip_odds, tip_odds + margin) last_step = 'Determining stake and placing bet' odds_stake = prices_and_stakes[:stakes][odds_to_execute_at.to_s] bet.executed_odds = odds_to_execute_at.to_s # stake will always fill the max available or the limits of our stake management - whichever is the lower. stake = bet.exchange_account.optimal_stake(bet, exchange.minimum_stake, odds_stake) raise 'Optimal stake is zero. Not taking bet' if stake.zero? bet.outcome = 'open' bet.stake = stake bet.expected_value = Bet.calculate_expected_value(stake, odds_to_execute_at) if bet.exchange_account.can_bet? bet.exchange_bet_id = exchange.place_bet(bet, stake) end else bet.log << '[Bet placement] Tip odds not found in latest prices, nudge too far out' end rescue Exception => e x = "Placement #{bet.placement_attempts}--->Last step: #{last_step}. Error: #{e.message}. Skipping" bet.log ||= [] bet.log << x bet.outcome = e.message.include?('PERMISSION_DENIED') ? 'errored' : 'skipped' ensure bet.save! end end end