AWP auction-winning bot

A program that bids on parts-package auctions automatically. It evolved from Selenium, through six headless browsers, to direct endpoints with no browser at all. It plays like a sniper, because it never bids first, outbids with a randomised step and holds a hard price cap. It feeds won packages into Stacja.

99.9%
auctions won
~50 ms
offer refresh
~10 ms
bid execution
  • Python
  • CustomTkinter
  • Selenium
  • Web scraping
  • API
  • Multithreading
AWP auction-winning bot

Overview

The company buys parts in packages won at auction. An auction ends at 16:00:00, and whoever has the highest bid by that second wins. Timing decides it, because the last seconds matter most.

I wrote AWP, a program that bids for us. The name comes from the sniper rifle in CS:GO, because the whole game is one well-aimed shot at the end. It runs on Python with the CustomTkinter library and works the same on Windows and macOS.

The main AWP window: a table of available auctions at the top, bidding and thread settings below.

Three versions

The program reached its current form in three steps, and each one removed the bottleneck of the last.

  1. Selenium. The first version opened every auction and clicked through all the offers, and to read the minimum price it had to open the details. It ran slowly, because every click waited for the page to load and render.
  2. Six browsers. The second version was multi-threaded. It opened six headless browsers and split the work between them: some refreshed the offers, others placed bids.
  3. Direct endpoints. The current version meant working out what gets sent to the auction server, and how. I found the endpoints for placing a bid and for refreshing offers, so the browser was no longer needed. Optimisations followed. Today the only brake on the program is the money cap it's allowed to bid up to.

Bidding strategy

The program never bids first. As long as nobody raises the price, AWP waits. Only once someone else places a bid does it outbid them right away, so we don't drive the price up ourselves.

You set the rest of the behaviour at the bottom of the window:

  • minimum and maximum price step, with the program drawing a random value between them, so it doesn't always raise by the same amount,
  • maximum price it's allowed to bid up to,
  • outbid only after someone else's bid, described above,
  • start moment, when the program begins placing shots.

The program splits into two tables. Filtering and a list of all available auctions sit at the top. Below them are the bidding settings for the offers we take part in.

Threads and refreshing

At launch you set how many threads to give the program. Each active offer gets one dedicated thread just for placing bids, so nothing blocks it. The remaining threads refresh the offers.

With eight threads and four offers, four threads shoot, one per offer, and four refresh. I spread the thread starts evenly over time, so at any moment one of them is just finishing a refresh. As a result offers refresh about every 50 ms, and firing a bid takes about 10 ms.

The third version bids straight through the endpoints, with no browser. The only brake left is the money cap it's allowed to bid up to.

Results

  • The third version bids directly through endpoints, with no browser, so it reacts far faster than the Selenium versions.
  • The sniper strategy means the program never bids first, outbids with a random price step and holds a hard cap, so it doesn't overpay.
  • The program wins 99.9% of the auctions it enters.
  • Offers refresh about every 50 ms and a bid fires in about 10 ms, straight against the endpoints.
  • One app for Windows and macOS.
  • Won packages go straight into Stacja, which processes them into parts.