Tuesday, 9 March 2010

Comparing the APIs: what's the best way to access Betfair from Python?

(Just a quick break from the blog series on building a Betfair bot: this is a quick comparison of the various options for accessing the API through Python.)


In the examples I've used to date, I've used either Betfair's own Excel plugin, or I've used Resolver Systems' own Betfair plugin (which I've modified to add additional features to...) But Resolver's own plugin may not be the best for accessing Betfair – even from within Resolver One – and it does come with a number of disadvantages: it is Windows only, for instance. But what are the other options:


Well, there's the Python Betfair Library, hosted on Google Code, and last updated in mid-2008. The readme suggests it will not be completely simple to install under Windows, but as I'm using my Ubuntu box at the moment, that isn't likely to be a serious problem. So, OK, let's download and untar it. Looking into the example code, it looks like it operates in a very similar way to the Resolver libraries:


exchange = Exchange()

exchange.login(username, password)

# How to call get_account_funds

funds = exchange.get_account_funds(Exchange.EXCHANGE_UK)


Yep: all similar here. If it works, and is easily cross-platform, this might be a goer. OK: let's run the example code:


python example.py


Uh oh:


ImportError: No module named ZSI


OK: this may not be quite as simple as I thought. ZSI is the Python Web Services project (Zolera SOAP Infrastructure). It's no big deal to install this on my box, but it does explain with these libraries might struggle to be easily cross-platform. Is ZSI/pywebsvc in the Ubuntu packages? Yep, it's python-zsi (as you might expect). A simple sudo apt-get install python-zsi is all that's needed.


Let's run again. It's asking me for my username and password – this is good. And now I wait... and I wait...


...lots of errors followed by...

socket.gaierror: [Errno -2] Name or service not known


Grr: let's run again. It's always hard to know whether the problem is Betfair's (often down, particularly for the free service, API) or whether it's because the PythonBetfairLibrary is out of date.


This time it's better: it's giving me lots of information on markets that I don't have positions in. Then it craps out on a 'MARKET_CLOSED' error, that I can at least trap. Initial impression: promising (the syntax looks fine), but it's not been updated for a long while, and it doesn't seem to trap errors very well.


Let's try a slightly more complex test: I have a Python app for simple market making, that I've optimized for golf tournaments. Normally, I run this in Resolver. But there's no reason it cannot run it as a standalone application – with the proviso that I lose the pretty display.


[25 minutes later]


Well, that was simpler than I thought. The Python Betfair Library works pretty well, with two major provisos. Firstly, it doesn't trap errors particularly well. I may do a little bit of work to allow it to throw out more useful exceptions. Secondly, it's sloooooooooooooow. (This can be an advantage, as you are less likely to accidentally exceed 60 API requests per minutes.) Perhaps it's because I'm used to the Windows native Betfair DLL that backs up the Resolver libraries, but taking 20-30 seconds to execute and output the results of GetAllMarkets (against less than 2 for the native DLL) is pretty slow. It was hard to market-make more than 6-7 golfers in an efficient manner, against to 30-40 I manage using the Resolver libraries. There is nothing intrinsically slow about using pure Python libraries, so I shall do a little investigation, and see if it's anything about my setup that is causing the slow performance...


Which bring us to the library at BespokeBets.com. This can be got here: http://bespokebots.com/showme.php?id=betfair_api_python.zip. It's a pretty simple library, that uses Python's built-in url2 library to do web service queries:


bot = API() # create new api object
print "Login:", bot.login(username, password)
bot.get_account_funds()


The code behind the library is pretty simple: this is a very thin wrapper around the Betfair API. (I was impressed to note that there is no Betfair DLL dependence, and everything fits into a few hundred lines of code. Nice work.) And this thin-ness brings a cost: it implements only a limited subset of the API – there is no GetDetailAvailableMktDepth or GetMarketProfitAndLoss for example. That said, extending this library would be simple, and it certainly seems capable of 4-5 simple API calls a second, which makes it quite a lot faster than the PBL.


I am not going to re-implement my market making system, because a small part of the algorithm relies on GetMarketProfitAndLoss. But in theory, working around that would not be difficult.


For now, I am going to stick with the Resolver libraries – which are, of course, just a thin interface round Betfair's own .NET ones: but for those in a non-Windows environment, both the Python Betfair Library and BespokeBot's library will work well. For anyone playing with building my bot, very few code changes should be necessary...


And now I need to get back to work...

Thursday, 4 March 2010

Building Our First Betfair Bot IV - Talking to the API and storing your bets

*EDIT: In this piece, I originally said you needed to use the paid API to place bets; this is no longer the case*

In the last missive, I wrote about how we want to store data. In theory, we could use Betfair itself to store data – and pull everything we need on an
ad hoc basis. So: if we want to see which bets are open, we'd query Betfair, and it would return a list of betIDs. But, this comes with two major problems: speed and cost. Betfair can charge as much as 0.5p for certain data intensive API calls: you need some pretty amazing programming and trading skills to overcome those costs.

So, our 'bot needs to store as much information locally as possible. And this means a (very simple) database – in our case SQLite. In today's missive, we're going to put a bet on, and then store it in our database.

The first thing we'll need is a programming language. I am going to be a little Windows-centric and use IronPython – the Microsoft implementation of Python 2.6. You'll also need this zip file: betfairdatabase.zip. This contains the following files:

betfair.py

lib.py

betfair.dll

→ these are the IronPython / Resolver Betfair libraries

SystemData.SQLite.dll

→ The .NET ADO SQLite connector

bettinghistory.s3db

→ An empty SQLite database

database.py

→ our database library

What we'd like to achieve (ultimately) is to put bets on, and then put them in a table that looks something like this:




After you've installed IronPython, it's time to have a play. For simplicities sake, copy the contents of the above folder into your IronPython directory. Now, run 'ipy.exe', which you'll find in the selfsame directory. You should get something that looks like this:




OK: Let's import the database module:


>>> import database


And create a new database connection object:


>>> dc = database.database()


Open the database file, so we can interact with it:


>>> dc.open('bettinghistory.s3db')


OK: so, what can we do with our database connection? Well, firstly we can record our betting activities. Let's set-up and record a bet:


>>> import betfair

>>> betfairGateway = betfair.BetfairGateway('username', 'password', productId = 22)

OK


(This assumes you have a Betfair paid API subscription. If you don't, you'll be reading, rather than doing.)


>>> betfairMarket = betfairGateway.GetMarketById(20692870)


We've set up a betfairMarket object for the winner of the World Cup 2010 (which, slightly surprisingly, Spain is the favourite to win.)


Shall we see who the runners are?


>>> for runner in betfairMarket.GetRunners()

... print runner, runner.Id, runner.Back

...


The screen should look something like this:




Now, I'm working on the theory that – if England moves out to 10-1 (11.0 in Betfair speak) – then I want some of that action. (Of course, the current price is 7.0, so that isn't very likely right now... but still...) To do this we have to get hold of the Betfair runner (selection) object:


>>> selection = betfairMarket.getRunner(27)


(We've gotten the selectionID by looking at the print-out above, and entering it manually. You'd probabaly want to iterate over GetRunners() until you got the object you wanted... but this will do for now.) And let's check we have the right object:


>>> selection, selection.Back

(England, 7.0)


Perfect: that looks like the right object, and we need to pass this object to the betfairGateway if we want to put a bet on:


>>> response = betfairGateway.Back(selection, 11, 2)

OK


The first thing we pass is the selection object, the second the desired odds, and finally the amount (in this case, the £2 minimum).


Now, the Betfair bet response object is a complex beast, but the only bit that is really of interest to us is the array of results. Let's pull it out:


>>> betsArray = response.betResults


And see how many bet objects it contains:


>>> len(betsArray)

1


No great surprise here: the bet we've put into the system is so far out the money I'd be surprised if there was more than one object: that of an unmatched bet. (If you were to offer to back England at 6, with £100,000, then you would get an awful lot of bet objects in return: one covering the bets matched at 7.0, one at 6.9, etc.


But let's take a look at the bet object we've gotten here:


>>> bet = betsArray[0]


Ha ha! Now we (finally) have an object with the details of our bet. Let's see if it was succesfully placed into the system (i.e. that the market is still live, and we still have money in our account):


>>> bet.success

True


Excellent. We've placed the bet. That's a good start. Was it matched?


>>> bet.sizeMatched

0.0


No surprise: it wasn't matched. It's sitting in the Betfair system waiting for someone to offer 10-1 on England. Finally, before we call it a wrap for the evening, let's store this bet in our database:


>>> dc.openBet(bet, 20692870, 27, 200, 11, 'BACK')


The openBet method takes the following paramaters: bet object, marketID, selectionID, amount (in pence), odds (a float), type (either 'BACK' or 'LAY').


Tomorrow – or in a few days – we'll play a little more with putting bets into the system, and reading the responses. But, hey, pat yourself on the back, you've just put some money on England to win the World Cup :-)