Vous êtes sur la page 1sur 95

Imperial College

Department of computing

STRATEGY TESTING IN BETFAIR MARKETS


C HRISTOPHER B ALDOCK
6/16/2009

Online betting exchanges have provided a new model for sports gambling. Through the use of
technology a customer can back or lay bets on multiple markets throughout the duration of the
event. Historical data for in-play markets has recently become available.
This project aims to develop a scripting language which can specify sport betting strategies. A
flexible framework is provided to execute these strategies against historical price data.

Strategy Testing in Betfair Markets

ACKNOWLEDGEMENTS
I would like to thank my supervisor Tony Field, for his useful ideas and encouragement throughout
the project. I would also like to thank my second marker Ashok Argent-Katwala for our insightful
discussions on software engineering and database design.
Chris Baldock, London, June 2009

Strategy Testing in Betfair Markets

TABLE OF CONTENTS
1 Introduction ......................................................................................................................................... 6
1.1 Contributions ................................................................................................................................ 7

2 Background .......................................................................................................................................... 8
2.1 Online Betting Exchange ............................................................................................................... 8
2.1.1 Events ..................................................................................................................................... 8
2.1.2 A Bet ....................................................................................................................................... 8
2.1.3 In Play Markets....................................................................................................................... 9
2.2 Betfair.......................................................................................................................................... 10
2.2.1 Events ................................................................................................................................... 10
2.2.2 Pricing Models...................................................................................................................... 10
2.2.3 Betfair API ............................................................................................................................ 11
2.3 Fracsoft ....................................................................................................................................... 11
2.3.1 Historical Data ...................................................................................................................... 11
2.3.2 Pricing Model ....................................................................................................................... 12
2.4 Betting Strategies ........................................................................................................................ 13
2.4.1 Staking .................................................................................................................................. 13
2.4.2 Arbitraging ........................................................................................................................... 13
2.4.3 Hedging ................................................................................................................................ 14
2.5 Betting software.......................................................................................................................... 14
2.5.1 Tools To Test Strategies Against Historical Data .................................................................. 14
2.5.2 Tools To Execute Strategies Against Live Data..................................................................... 15
2.6 Parser generator ......................................................................................................................... 16
2.6.1 ANTLR ................................................................................................................................... 17

3 Language Design ................................................................................................................................ 18


3.1.1 Events ................................................................................................................................... 19
3.1.2 Bets ...................................................................................................................................... 19
3.1.3 Conditions ............................................................................................................................ 20
3.1.4 In-Play Events ....................................................................................................................... 20
3.1.5 Functions .............................................................................................................................. 21

Strategy Testing in Betfair Markets

3.2 Domain Specific Vs API................................................................................................................ 21


3.3 Summary ..................................................................................................................................... 22

4 Language Implementation ................................................................................................................. 23


4.1 Grammar Rules ........................................................................................................................... 23
4.2 An Example Script ....................................................................................................................... 28
4.3 Tree Construction ....................................................................................................................... 29
4.4 Tree Walking ............................................................................................................................... 29
4.4.1 Semantic Analysis: Round 1 ................................................................................................. 30
4.4.2 Semantic Analysis: Round 2 ................................................................................................. 34
4.4.3 Strategy Interpreter ............................................................................................................. 35
4.5 Summary ..................................................................................................................................... 38

5 Evaluating Strategies .......................................................................................................................... 39


5.1 Methodology............................................................................................................................... 39
5.1.1 Framework Design ............................................................................................................... 39
5.1.2 Placing Bets .......................................................................................................................... 41
5.1.3 Low Initial Overhead, Small Memory Storage, Quick SQL ................................................... 41
5.1.4 Java Reflection For Built-In Functions .................................................................................. 42
5.1.5 Inferring Event Data ............................................................................................................. 43
5.2 Database ..................................................................................................................................... 44
5.2.1 Design................................................................................................................................... 44
5.2.2 Challenges In Porting Fracsoft Data ..................................................................................... 47

6 10 strategies tested............................................................................................................................ 49

7 Discussion And Future Work .............................................................................................................. 58


7.1 Scripting Language ...................................................................................................................... 58
7.1.1 Implementing Types............................................................................................................. 58
7.1.2 Incorporating Form Statistics ............................................................................................... 58
7.1.3 Ability To Mix And Match Strategies.................................................................................... 59
7.1.4 Additional Error Handling .................................................................................................... 59
7.2 Testing Application...................................................................................................................... 59
7.2.1 GUI ....................................................................................................................................... 59

Strategy Testing in Betfair Markets

7.2.2 Separate Function Libraries for Different Event Types ........................................................ 60


7.3 Data ............................................................................................................................................. 60
7.3.1 Implement Event Data ......................................................................................................... 60
7.4 ANTLR .......................................................................................................................................... 60
7.4.1 Extend tree parser................................................................................................................ 60
7.5 Extending For Further Sports ...................................................................................................... 61
7.6 Extending To Execute Strategies Live.......................................................................................... 63

8 Conclusions ........................................................................................................................................ 64

Appendix A ............................................................................................................................................ 65

9 Bibliography ....................................................................................................................................... 95

Strategy Testing in Betfair Markets

1 INTRODUCTION
A betting exchange acts as a broker between parties for the placement of bets. Gamblers may
choose from a wide variety of events and suggest their own odds for the outcome. Betfair (1) is a
betting exchange which specialises in sports betting.
A sporting event will typically contain several different betting markets. As an example, a Football
match will contain a Match Odds market, a Correct Score market and an Over/Under 2.5 Goals
market, as well as others. Each market will have several different selections, each one representing a
different outcome.
For each you may back (bet the selection will win) or lay (bet the selection will lose). For example,
the Match Odds market of a football match contains three different selections to reflect the possible
outcomes. A Home Win, an Away Win and a Draw.
The operator makes a guaranteed profit by taking a commission from the winners net profit.
Considering every matched bet will have a guaranteed winner, this is truly an excellent business
model.
A novel feature of a betting exchange is the addition of in-play betting markets. Such a market will
allow customers to place bets even whilst the event is in progress. Whereas the odds in a normal
market will be a speculation of the competitors past performance, an in-play market will accurately
reflect the current state of the event. A goal, try or point for example will have immediate effects on
the events odds. This feature makes it possible to form complex betting strategies which are void of
an opinion and instead take advantage of market trading dynamics.
Different people will employ various strategies with a view to making a profit. The most basic will do
nothing more than place a single bet on the outcome for a specified event, such as Back Arsenal to
beat Chelsea in todays game. Others will be more complicated and define general rules that may
be applied over a large number of user-specified events. An example of such a strategy could be as
follows: For all Horse races where Red Rum is competing, back Red Rum to win for 10 before the
race begins. At any point during the race, if the odds to lay Red Rum drop by 20% then lay Red Rum
for 10 and place an additional back to win for 5 on the current market favourite.
Such a strategy could be applied to a betting exchange but it would require a keen eye and a quick
mind to implement. More importantly, without exhaustively testing this strategy we would have no
way to determine if it produced a long-term profit. For serious gamblers it would be useful to first
formulate a strategy into a logical form and then execute it against historical market data. This
would allow us to formulate a winning strategy without the unwanted side-effect of losing money!
The idea of this project is to create a strategy description language for specifying sophisticated
betting strategies. In order to evaluate our strategies, a testing engine is developed which can
execute our strategies against fully time-stamped historical betting data. Fracsoft (2) is the only
third-party licensed vendor of Betfair historical data and supplier for this project. They have been
collecting fully time-stamped market data since early 2006 which they offer to the public for a fee.

Strategy Testing in Betfair Markets

1.1 CONTRIBUTIONS
Chapter 3 details the main issues considered when designing a language to specify market
independent betting strategies.
Chapter 4 discusses the implementation of our language as well as more intricate design features
Chapter 5 discusses the implementation of a testing engine which can execute our formulated
strategies against fully time-stamped market data. A measure of the strategies performance is
produced as output.
Chapter 6 will explain and evaluate ten different strategies at increasing levels of complexity. Each
strategy will attempt to build on from the previous as well as demonstrate an additional feature of
our language.
Finally, the limitations of our language and testing engine, as well as areas for further work are
discussed in Chapter 7.

Strategy Testing in Betfair Markets

2 BACKGROUND
2.1 ONLINE BETTING EXCHANGE
Prior to an online betting exchange, bets were placed with a bookmaker, an individual or
organisation who would accept bets on the outcome of an event. Providing that the bookmaker
accepts bets in the right proportions, a profit can be achieved regardless of the outcome. This is
known as the auctioneer problem, the specific details are beyond the scope of this project.
An online betting exchange is a form of bookmaking whereby the operator can offset all risks
through the use of technology. Similar to a stock exchange, the operator acts as a broker between
parties for the placement of bets. Gamblers may choose from a wide variety of events and suggest
their own odds for the outcome. Opposing bets are matched anonymously by the exchange which
also handles the transfer of money once an event has been concluded.
The operator makes a guaranteed profit by taking a commission from the winners net profit.
Considering every matched bet will have a guaranteed winner, this is truly an excellent business
model.

2.1.1 EVENTS
A betting exchange will typically administer a multitude of different events. These can vary from
football matches to the outcomes of popular television shows. Each event will be associated with
several different betting markets.
As an example, a football match has a match odds market, a correct score market and an over/under
2.5 goals market, as well as others. Each market will have several different selections, each one
representing a different outcome. For each you may back (bet the selection will win) or lay (bet the
selection will lose). For example, the match odds market of a football match contains three different
selections to reflect the possible outcomes. A home win, an away win and a draw.

2.1.2 A BET
A fixed-odds bet is the most common form of bet offered by a betting exchange. As the name
implies, the odds at which a bet is placed will remain constant until the termination of the event.
When contemplating a fixed-odds bet, the user will know exactly how much money will be returned
if the bet is successful. This value is determined from the odds (i.e. the price) and stake by the
formula:
=
For example, a 10 bet at odds of 3.6 would return 36 with a profit of 26 if successful. The same
bet, if unsuccessful, would result in the 10 stake being lost for a profit of -10.

Strategy Testing in Betfair Markets

A lay bet has the opposite outcome. A 10 bet at odds of 3.6 would return a profit of 10 if
successful. Because you are essentially playing the role of bookmaker, you stand to lose 36 if your
bet is unsuccessful.
For a bet to be fair to both parties, the odds must be an accurate reflection of the true probability of
the outcome occurring. The relationship between the fair price and probability is:
=

For example, a fair coin toss has a 50% chance of yielding heads. If you were offered odds of 2.0 for
this event, the bet could be considered fair.
A bookmaker will typically estimate the fair odds of a game before modifying the probabilities of
each outcome in his favour. To guarantee a profit, the bookmaker will ensure that the sum of all
outcomes is greater than 100%. As a consequence, if the gambler is successful, he is provided with a
payout less than that paid by the true odds. The amount by which the actual book exceeds 100% is
known as the overround.
A betting exchange represents a free market where the prices are decided by the customers and not
the bookmaker. Very little over round exists and odds are typically around 20% better than those
offered by a bookmaker (3).

2.1.3 IN PLAY MARKETS


An In-play market involves betting whilst an event is in progress. At the start of the event all
unmatched bets are cancelled and a new in-play market is started. Betting is allowed right up until
the end of the event where the final outcome is known.
Whereas the odds in a normal market will be a speculation of the competitors past performance, an
in-play market will accurately reflect the current state of the event. A goal, try or point for example
will have immediate effects on the events odds.
This feature has opened the door to a range of exciting betting opportunities. By picking the right
moment you can secure your bet at far better odds than was previously possible. If the event is
going against your forecast then you have the potential to change your strategy altogether!
Although research has shown that a betting market is a good predictor on an events outcome (4),
the price movements and odds offered in an in-play market will always contain a speculative
component. This feature should allow us to form betting strategies which are void of an opinion and
instead take advantage of market trading dynamics.
Because of the recent availability of historical in-play data, we have the opportunity to evaluate
these strategies based on their performance against historical market data.

10

Strategy Testing in Betfair Markets

2.2 BETFAIR
Betfair is the worlds largest betting exchange and focus for this project; they have over 2,000,000
clients and boast a weekly turnover in excess of 50m per week (1).
Betfairs business model was inspired by the stock market where users could buy and sell market
odds as if they were share prices. Providing a platform could be created to balance supply and
demand, a gambler would choose whichever operator offered the best odds. Considering Betfairs
model represents a free market where little or no overround exists, the odds provided were
guaranteed to beat the other operators of the time the bookmakers.

2.2.1 EVENTS
Today Betfair still specialises in sports betting. However they also host a range of alternatives to
appeal to the mainstream consumer. For example, users may bet on the winner of the next general
election, try a spot of virtual roulette or take part in financial spread betting. Providing an event has
enough demand the potential exists for a new betting market.

Figure 2.1: Betfair market for the next UK General Election

Betfairs website displays the most popular fixtures on the main page with a tree structure on the
left hand side to navigate through the events on offer. A user will start by choosing an event type in
the left hand menu before progressing down the tree until a desired market is found.

2.2.2 PRICING MODELS


Betfair charges a commission on all winning bets. The commission is calculated from a users net
winnings, discount rate and market base rate from the following formula:
= (1 )
The market base rate varies by market but is generally set to 5%. The net winnings are the net profit
made from a single market. The discount rate varies from 1-60% and is determined by the number of
Betfair points accrued by a user.

11

Strategy Testing in Betfair Markets

Points are Betfairs way of rewarding its most active customers and are earned in proportion to their
betting activity the more they bet, the more points they accrue and the greater discount they will
receive.
As of September 2008, Betfair have introduced a controversial premium charge to its existing pricing
model (5). Accounts that have bet in more than 250 markets, which are in profit over a 60 week
period and have been charged a commission less than 20% of their net profits, are charged an
additional premium to make up the difference.
Betfair claims that this charge will affect only 0.5% of its customer base, however the model has
received widespread criticism as it only targets the most successful betters (6).

2.2.3 BETFAIR API


Betfair publish an API which enables users to develop applications which integrate with the Betfair
sports exchange (7). The API uses a SOAP XML interface to communicate directly with the Betfair
database. Using this interface, users can read live market data, place bets and check their account
statement without the use of a web browser.
Betfair offers a mixture of API packages with varying prices and licences. A free access API is offered
which allows users to access the majority of the APIs functionality with some limitations at which
services are requested. E.g. the rate at which you can access the current market price is capped to
five times a minute.
The personal full API normally costs 200/month and provides the user with unlimited calls to
Betfairs database. On top of the limitations posed by the different API packages, Betfair customers
are charged a data request fee if a large number of data requests are made within the same second.
At the end of every day, Betfair adds up the total number of API and website data requests the user
made in each second. If this number exceeds more than 20, the user will be charged an additional
0.1p per data request above 20. API calls and website refreshes send requests for different types
and quantities of data. Betfair therefore applies a weighting system to calculate the actual number
of data requests made in each second (5).

2.3 FRACSOFT
Fracsoft (2) is the only third-party licensed vendor of Betfair historical data. They have been
collecting fully time-stamped market data since early 2006 which they offer to the public for a fee.
The data includes both normal and in play market data for eight different sporting events: soccer,
horseracing, greyhounds, tennis, golf, snooker, basketball and cricket.

2.3.1 HISTORICAL DATA


Figure 2.2 shows several views from the Fracsoft historical data manager. This tool can be used both
to view and purchase historical market data.

12

Strategy Testing in Betfair Markets

Figure 2.2: Different views taken from Fracsoft historical data manager

The tool provides an event tree to search through the different markets as well as a view through
which purchased data can be analysed. The markets provided contain fully time-stamped price and
volume data with an interval of 1 millisecond. This data manager also provides the functionality to
export purchased historical data to XML.

2.3.2 PRICING MODEL


Data is priced per fixture (8), rather than per market. When a fixture is purchased the user receives
all of the associated markets. The price of data depends on the event type being purchased, as well
as an additional cost for the usage of in play markets. One aim of this project is to back-test
strategies against three very different sports: soccer, tennis and horseracing. Imperial College and
Betfair are willing to purchase any historical data needed for this project.
A table showing the different prices for these sports has been compiled in Table 2.1:
Sport

Country / Event

Market
type
Winner

Free Assoc
markets
To be
placed

Cost (Excl
in play)
From 15p

Cost (incl.
in play)
From 59p

Horse
racing

GB/IRE/AUS/USA/RS
A

Soccer

All major leagues

Match
odds

From 15p

From 68p

All tournaments

Match
odds

Next goal,
over 2.5
goals etc...
Set betting

Tennis

N/A

From 21p

Table 2.1: Price scheme for historical market data offered by Fracsoft

Description
The winner
and place
markets per
meeting
Assortment of
fixtures for all
major leagues
Player Vs
Player Match
Odds
markets.

13

Strategy Testing in Betfair Markets

2.4 BETTING STRATEGIES


A sport betting strategy is a structured approach to gambling intended to counter the inherent bias
held by bookmakers. A betting exchange however will contain very little bias so when we consider a
strategy we must instead consider a model which can increase the odds of winning in order to
produce a long term profit.
The concept behind an online betting exchange was initially modelled on that of a stock exchange
indeed, many similarities can be drawn between the two systems. Both provide a platform for
reducing the complexity of a trade, the back and lay options comparing to the buying and selling of
derivatives. It seems appropriate that betting strategies can be drawn from techniques used in
financial trading.
A strategy can be considered to come in both fundamental and technical varieties. Fundamental
strategies commonly are based on publically available information used to handicap events. A
bettor using a fundamental, or handicapping, strategy attempts to determine which horses, if any,
have probabilities of winning that significantly exceed the market-determined amount. Technical
systems require less information and use only current betting data. Bettors using a technical system
attempt to find inefficiencies in the market and bet on such overlays to yield a profit. For the aims
of this project we will concentrate on the latter.

2.4.1 STAKING
The most straightforward type of betting strategy is staking. In this system if there is a good reason
to believe that a particular outcome will occur and the odds offered represent value (i.e. the inferred
probability of the outcome is equal to or less than your prediction) then you simply place a bet on
that outcome. This strategy can be made more complex by stating precise conditions for a bet to be
placed e.g. on the 32nd minute of the game, if team A is leading then back team B to win. The
fundamental property of a stake system is that only one bet is made within the market, no matter
how complex.

2.4.2 ARBITRAGING
Arbitraging is a strategy used heavily within the financial sector and can be expressed in simple
terms as buy low, sell high (equivalently back high, lay low). By backing an outcome at a low
price (i.e. large odds) and laying the same outcome at a high price (i.e. small odds) the trader can
guarantee a profit on all outcomes of an event.
For example a simple arbitrage strategy might include backing a horse ninja turtle to win for 5 at
odds of 10. These odds imply that the subjecting probability of the horse winning is 10% (i.e. 1/10)
which is quite unlikely. The position of a horse however may change during the race itself. If at any
point the odds for the horse drop significantly below 10 there is an opportunity to arbitrage and
guarantee a profit. If, for example, the odds of the horse drop to 4 during the race, a lay bet can
guarantee a profit regardless of the outcome. A 9 lay bet will yield a profit of 9 (45-36) if the
horse wins and 4 (9-5) if it loses.

14

Strategy Testing in Betfair Markets

Betfair only charges commission on net winnings, which suits an arbitragers high-turnover, lowprofit strategy. The profit or loss for an arbitrager will typically be a small percentage of the total
amount of their combined back and lay stakes. It therefore makes sense that a relatively large
amount of capital will be required to make any meaningful amount of money.
When carrying out an arbitrage you always run the risk that an events outcome will turn against the
direction of your strategy. If this happens it may be impossible to close your bet on more favourable
odds leaving a large unwanted bet on the event. This is just one situation where hedging may be
used to reduce the overall risk.

2.4.3 HEDGING
A hedge is a position established in one market in an attempt to offset exposure in another. By
carrying out a hedge the overall level of risk is reduced but at a cost to the final profit. An example of
a hedged bet could be as follows:
Arsenal is playing Chelsea. It is currently the 80th minute and the score is 1-0 to Arsenal. At the
beginning of the game you wisely placed a 5 bet at odds of 3.2 for Arsenal to win. If Chelsea does
not score in the final 10 minutes you stand to make a profit of 11. At this point it is very unlikely
that Chelsea will go on to win. However, there is always a chance that the 1-1 draw could occur. You
check out the final score market and notice that the 1-1 outcome is being offered with odds of 4.0.
In this situation you could place 4 on the 1-1 outcome to greatly reduce your risk and most likely
secure a profit whatever the outcome. If Arsenal wins you are left with a profit of 7 (11-4). If
Chelsea goes on to score the final goal then your profit is also 7 (12-5), you win either way.

2.5 BETTING SOFTWARE


Various tools exist which allow the user to execute a betting strategy against live market data
however none have utilised a domain specific language to formulate their strategies. Additionally,
very few tools exist to specify and test a strategy against historical market data. In this section I will
evaluate the current state of the art and consider what aspects could be improved.

2.5.1 TOOLS TO TEST STRATEGIES AGAINST HISTORICAL DATA


A similar project to the one proposed in this report was carried out in 2008 by an Imperial MSc
student, Simon Thomas Ansell. Simon created a scripting language that could specify football
strategies for a limited number of markets. He also developed an application through which he could
test his strategies against historical betting exchange data. The main features of his language were
as follows:

A filter mechanism to specify which football matches should be evaluated


The ability to define back or lay bets for in-play betting markets
The ability to determine if a previous bet has been placed
The ability to assign values to a variable (Although only once!)
The ability to evaluate simple math expressions
Support for the creation of IF conditional statements

15

Strategy Testing in Betfair Markets

There are some limitations to this system. Firstly, the implementation was only capable of specifying
and testing football strategies. The language itself could potentially be extended for other sports,
although the grammar and database design were restricted to use only three different football
markets: match odds, correct score and under 2.5 goals.
The grammar utilized embedded actions instead of the natural choice of an abstract syntax tree. This
required the testing application to be integrated within the grammar file. This makes both
components dependent on one; modifications to objects in the testing application would sometimes
necessitate modifications to the file and vice versa. These features reduced the modularity and
extensibility of the system.
Furthermore, the lack of AST imposes restrictions on future development. If the scripting language
were to be extended, for example by adding function calls or loop structures, the parser would have
to execute the same pieces of code multiple times. Each time an input script called a function that
function would have to be reparsed.
The scripting language provides some basic functionality, but there are a number of limitations:

A bet can only be placed once. This is one factor that restricted the language from efficiently
carrying out a martingale betting strategy i.e. a repetitive strategy which increases odds in
order to recoup a loss.
A bet must be contained within a conditional statement. The language would be far more
expressive if bets could be placed irrespective of condition.
Stakes and Odds can be expressed only as variable amounts. A more expressive language
would allow these parameters to be declared as a function of other variables. For example,
it may be desirable to place a new bet with a stake equal to twice of your existing liability.
Variables exist, but they must be assigned prior to executing a strategy. The value of each
variable may not be overridden during the execution of a strategy.
There are no user-defined functions. Another feature that would greatly enhance the
extensibility of the language.

Information found on various betting forums would suggest that others have developed their own
in-house strategy testing applications. Not surprisingly the details such software is hidden with no
software freely available.

2.5.2 TOOLS TO EXECUTE STRATEGIES AGAINST LIVE DATA


A market exists for tools which can execute strategies against live market data (9). These tools have
been in development for several years and offer a myriad of functionality over and above that
offered by a betting exchange.
None of these services have created a formal language for specifying a betting strategy. Instead, a
user is provided with a series of rules. Each rule will represent a condition which the user can
specify. The user can use the default set of rules provided by the software or create their own using
Microsoft Excel. The combined set of rules will create a basic strategy for placing a bet. Figure 2.3
shows a subset of rules available for the Bet Angel trading software (10).

16

Strategy Testing in Betfair Markets

Figure 2.3 A simple strategy defined using the BetAngel trading software

The rule set in the example above says the following: If the current odds are greater than or equal
to 2.00 and smaller than or equal to 5.00 and the weight of money is less than or equal to 100% then
place a back bet at odds of 2.00. Once the strategy is specified, the market and runners on which
the strategy is applied are chosen.
These tools offer some novel functionality which should be considered for this project. A nice
feature is the ability to place bets at smaller amounts than Betfairs 2 minimum (This works by
exploiting a loophole within Betfairs service). By placing a dummy bet of 2 at odds of 1000, the
user can guarantee that no-one will match his bet. By later updating the stake of this unmatched bet
to 2.10, the exchange will place an additional bet of 10 pence. At this point if you adjust the odds of
the 10 pence bet to something more suitable, the exchange will match the bet! This is a little tedious
by hand but can be automated effortlessly by software.

2.6 PARSER GENERATOR


A parser generator is a language tool which analyses the formal description of a language (normally
specified as a grammar) and generates the source code for a parser. The parser in-turn can be used
as a translator to convert the input sequence of one language into the output sequence of another.
This translation phase can be split into two separate stages. The first is called lexical analysis which
converts a sequence of characters into a sequence of tokens. The second phase is syntactic analysis
where the stream of generated tokens is analysed to determine their grammatical structure.
For the translator to create any useful output or additional processing, an extra stage is required.
This can be carried out in two different manners. One way is to embed a series of actions (code)
within the user specified grammar. When the parser recognizes a particular expression, an action
within the expression rule will be executed.
The second should be utilised for any language which requires multiple passes. Rather than re-parse
the input characters and grammar, it is usually more convenient to construct an intermediate form
of the input. This abstract syntax tree (AST), is a highly processed, condensed version of the input.

17

Strategy Testing in Betfair Markets

Figure 2.4: Translation data flow; Arrows represent data structure flow, squares represent translation
phases (11)

The figure above illustrates the basic data flow of a translator that accepts characters and emits
output. The lexical analyzer, or lexer, breaks up the input stream into tokens. The parser feeds off
this token stream and tries to recognize the sentence structure. The simplest translators execute
actions that immediately emit output, bypassing any further phases. A more complex translator will
utilize an abstract syntax tree (AST) and tree walker prior to emitting its output.

2.6.1 ANTLR
ANTLR is a popular parser generator that utilises top-down LL (*) parsing. It has a large online
community and excellent documentation. ANTLRs main features are listed below:

ANTLR is the only parser generator which utilises LL (*) parsing. This, in contrast with LL (k)based parsers allow the look-ahead to roam arbitrarily far ahead, relieving you of the
responsibility of specifying k. (12)

Rules for ANTLR are expressed in Extended BackusNaur Form (EBNF)


ANTLR supports generating code in: Java, C, C++, C#, Python, Objective-C
The ANTLR software is open source under the BSD licence
Support is provided to run ANTLR under eclipse

One additional feature of ANTLR is the availability of ANTLRWorks, a GUI development environment.
This software sits on top of ANTLR and simplifies the navigation, editing and debugging of grammars.

18

Strategy Testing in Betfair Markets

3 LANGUAGE DESIGN
Consider the following three strategies. The first will represent a football strategy, the second a
horse racing strategy and the third a tennis strategy. By analysing all three we can build up a set of
features that would be desirable in a betting language. Later in Section 6 we evaluate these
strategies, as well as others, against historical betting data.

A FOOTBALL STRATEGY
For all Football matches in the Barclays premiership where Arsenal is playing Chelsea
1) At the beginning of the match, back Arsenal to win for 50 if their odds are greater than 3.2
2) If Chelsea scores during the first 40 minutes, then back the draw for 50
3) If Chelsea is the favourite team in the last ten minutes then back the current score for 100

A HORSE RACING STRATEGY


For all Horse races at Ascot
1) At the beginning of the race, back the second favourite horse to win for 100 if the odds are
greater than 3.0
2) If these odds are not available then back the favourite to win for the same stake.
3) In the last 1 minute of the race, hedge our initial bet by backing the current favourite horse
to win for 25.

A TENNIS STRATEGY
For all Tennis matches where Nadal is playing
1) At the beginning of the match, back Nadal to win for 100 if his odds are greater than 1.5
2) Seek an arbitrage opportunity throughout the remaining match. If at any point we can obtain
lay odds which are 20% smaller than our initial back bet, place this bet.

All three strategies are fairly simple, but together illustrate enough features to build a sophisticated
betting language. Some features which we can extract from these examples are discussed below.
Other features not mentioned are covered in our implementation.

19

Strategy Testing in Betfair Markets

3.1.1 EVENTS
The first line in each of our above strategies is a sentence which describes the events we wish to bet
on. For example, the line For all Football matches in the Barclays premiership where Arsenal is
playing Chelsea implies that we should evaluate our strategy only against football matches where
Arsenal is playing Chelsea and the competition is the Barclays premiership. All other events which do
not meet these criteria should be discarded.
In order to define these criteria in our language, our implementation will require a filter mechanism
for specifying the events we wish to apply our strategy to. This event filter will require several levels
of detail. In its broadest sense, it will only specify the sport and nothing else. In its more restrictive
form, the filter will specify a unique fixture with no double.
In order to apply our strategies against any event type, all mechanisms used to query the state of an
event will be implemented as market-independent. Additionally it would probably be unwise to
apply a football strategy against a football and tennis market simultaneously. For this reason a
strategy will be restricted to evaluate only one sport at a time.

3.1.2 BETS
Each numbered instruction in the three strategies above, represent the criteria for the placement of
a bet. In order to place a bet under a betting exchange we must specify the following, the:

market in which we place our bet


selection which we are betting on
direction of our bet (i.e. back or lay)
odds and stake

When placing a bet, it is anonymously aggregated with all other bets on the Betfair exchange. Bets
with the same odds are grouped together and the most competitive three are displayed. The odds
and volumes displayed in Figure 2.1 show the most popular bets for the next UK General Elections.
There is no guarantee that our bet will be matched by the exchange, in fact it will only be presented
to the public if it is competitive. For example, placing a back bet at odds 500 for Chelsea to win prior
to the start of a football match is completely unfair and so the chance of someone laying such a bet
is very unlikely.
To simplify the betting process, our implementation will assume that all bets that are placed will be
matched at the best available odds. This is a fair assumption as we are simply matching a bet that
was previously offered on the Betfair exchange. An additional benefit is that the user will not be
required to specify the odds when placing a bet, thus only values for market, selection, direction and
stake will be required.
For the bet in instruction 1 of our football strategy, the market is Match Odds, the selection is
Arsenal, the direction is Back and the stake is 50. In this instance we only wish to evaluate our
strategy against matches where Arsenal is playing Chelsea, because of this we can guarantee that
the selection Arsenal will be a legal selection for all fixtures defined by our event filter.

20

Strategy Testing in Betfair Markets

If however we wished to create a strategy which acts on ALL football matches then we will require a
placeholder which will later be replaced by the appropriate String at runtime. An example selection
placeholder for a football match would be Home or Away, a horse race will have a placeholder
Runner(n) where n specifies the stall number of the horse. Our implementation will include
placeholders for markets and selections for football, horse racing and tennis events.

3.1.3 CONDITIONS
Perhaps the logic required by our strategies would be more naturally expressed through pseudo
code. The following is an alternative means to express the conditions of our example horse racing
strategy1:
1) IF (TIME == 0 AND SecondFavHorseOdds > 3.0) THEN PlaceBet(SecondFavHorse, 100)
2) IF (TIME == 0 AND SecondFavHorseOdds <= 3.0) THEN PlaceBet(FavHorse, 100)
3) IF (TIME == LastTwoMinutes) THEN PlaceBet(FavHorse, 25)

Any condition can be translated into a series of IF statements. The addition of an ELSE statement will
help us to express the same logic whilst better conserving the natural flow of a strategy. Therefore
our strategy can be expressed equivalently as:
IF(TIME == 0) THEN
IF(SecondFavHorseOdds > 3.0) THEN PlaceBet(SecondFavHorseOdds, 100)
ELSE THEN PlaceBet(FavHorse, 100)
IF (TIME == LastTwoMinutes) THEN PlaceBet(FavHorse, 25)

Condition 1 states that we should only place a bet on the second favourite horse, if the current time
is 0 (i.e. the start of the race) and the odds for the horse are greater than 3.0. Our implementation
will therefore require the ability to evaluate expressions as well as the ability to query the current
state of the market.
For all three bets, the condition can include an element relating to the time of the event. This is not
always necessary; our second figure illustrates the same three bets including only two time elements.
Our implementation will allow (but not enforce) the user to query the current time of an event when
expressing a condition2. Additionally a condition can be utilised without specifying a bet, just as a bet
can be placed without requiring a condition.

3.1.4 IN-PLAY EVENTS


We discussed in section 2.1.3, how a significant occurrence, such as a goal, try or point will have
immediate effects on an events odds. For example, if a tennis player wins a set in a tennis match
then his odds will quickly decrease to reflect his increased chance of winning.
Rule 2 of our football strategy states If the other team scores during the first 40 minutes, then back
the draw. In order to evaluate such a condition, we will require the ability to detect an in-play event
as it occurs.

1
2

This structure is used solely to illustrate the makeup of a condition and is not a reflection of the final implementation
Our language should accept the possibility of a strategy which places bets irrespective of time

21

Strategy Testing in Betfair Markets

This feature would be fairly straightforward to implement if time-stamped event data for goal, sets
and horse positions was readily available. Unfortunately this is not the case and instead our
implementation will attempt to infer such events from the price data alone (Section 5.1.5). This
functionality is provided to the user through the use of predefined functions.

3.1.5 FUNCTIONS
Rule 2 of our tennis strategy states Seek an arbitrage opportunity throughout the remaining match.
An arbitrage is a betting strategy which, if performed correctly will guarantee a profit on all
outcomes of an event (Section 2.4.2). In this instance, assuming that we backed Nadal to win in rule
1, an arbitrage will mean later laying Nadal to win at higher odds.
As this strategy can only be attempted on the successful placement of our original back bet, we must
be able to reference the other bets in our strategy. To achieve this, our implementation should allow
every bet to be given a label.
Rule 2 completes its statement with If at any point we can obtain lay odds which are 20% smaller
than our initial back bet, place this bet. This imposes a restriction on our arbitrage, i.e. it should
only be carried out if we witness lay odds which are 20% smaller than our original bet.
This condition can be evaluated as a function of our back stake, back odds and required return. In
order to allow a user to evaluate such complex expressions, our implementation will allow for the
creation of user-defined functions as well as user-defined variables.

3.2 DOMAIN SPECIFIC VS API


The objective of this project is to create a betting language which contains the above features. In
order to reduce the overheads required to formulate a strategy, we intend to build a simpler tool
than could be delivered by the use of an existing language and API. The reasons for this decision can
be illustrated with the following example:
A simple betting strategy using a Java API will look like this3:
import betAPI;
public class SimpleStrategy
{
public static void main( String args[] )
{
Strategy s = new Strategy(60);
while(s.notConcluded){
String home = s.getHomeTeam();
s.placeBet(BACK, Match Odds, home, 10);
s.concludeStrategy();
}
}
}

To simplify our comparison, the existence of an event filter has been excluded from both implementations

22

Strategy Testing in Betfair Markets

In order to both understand and write such a strategy, we would first need to be familiar with the
following:
1)
2)
3)
4)
5)
6)
7)
8)
9)
10)

What import means


What a class is
What an object is
What public means
What static means
What void means
What the new command does
What the dot operator does
What is a loop
What is a String, or an array of Strings

Alternatively, here is the same strategy using our domain specific betting language:
STRAT(60){
BACK(MatchOdds,Home,10);
exit;
}

The latter requires considerably less a prior knowledge and perhaps more importantly, only contains
statement which are applicable to our strategy. We therefore opt to develop a domain specific
language approach.

3.3 SUMMARY
This functionality is enough to create strategies sophisticated enough for most purposes. As the
implementation develops, more features will be added. Chapter 4 describes the implementation of a
domain specific language which is capable of specifying sophisticated betting strategies. Chapter 5
describes the implementation of a testing suite which can evaluate our betting strategies against
historical market data. In Chapter 6, ten different betting strategies are developed and used to
demonstrate the main features of our language.

23

Strategy Testing in Betfair Markets

4 LANGUAGE IMPLEMENTATION
This section describes the implementation for the most important features of our language. Often
rules will be simplified to aid explanation and some minor features may be omitted. For a summary
of all features see Section 0. Additionally, the full details of our implementation are shown in
Appendix A.

4.1 GRAMMAR RULES


The highest-level construct of ANTLR is a grammar a list of rules describing the correct structure (or
syntax) of a particular language.
We need to build a grammar that completely describes the syntax of our betting language. From this
grammar, ANTLR will generate a Lexer and Parser which can recognize syntactically correct
strategies. ANTLR will automatically issue errors for strategies with incorrect syntax.
A rule should be read from left to right with the order of statements depicting the order in which
they are parsed. The grammar rule where parsing begins is called the start rule. The start rule for our
betting language is:
prog:

eventFilter? varStat* strategy functionDef*

This rule states that a program will consist of an optional event filter, followed by zero or more
variable statements, a strategy and will terminate with zero or more function definitions.
The eventFilter rule defines which events the strategy should be applied to. If omitted, then the
program will continue to apply syntactic and semantic analysis but the strategy itself will not be
executed. This feature enables us to test the legality of our structure without requiring a connection
to the database.
The structure of an event filter is as follows:
eventFilter
:
'EVENTS' '{' eventFilterStatement '}'
;
eventFilterStatement
:
assignEvent (AND eventFilterExpression)* ';'
;
assignEvent
:
'EventType' '=' STRING -> ^('EventType' STRING)
;
eventFilterExpression
:
'('? filter eventFilterConditional ')'?
;
eventFilterConditional
:
'=' STRING
|
'LIKE' STRING
|
'IN' '(' repeatedString ')'
|
'LIN' '(' repeatedString ')'
;
repeatedString

24

Strategy Testing in Betfair Markets

:
;

STRING (',' STRING)*

('Event'|'Fixture'|'Runner');

filter

These rules state that an event filter must start with the text EVENTS {, followed by an event filter
statement with a terminating }. We only wish to test our strategy against one event type at a time
(you probably wouldnt want to test a football strategy against a horse racing market!). Therefore
the grammar requires us to provide an event assignment (i.e. EventType = Soccer). This is the
broadest filter possible and will apply our strategy against ALL Soccer matches present in our
database.
If we wish to narrow down this set of events, we can do so by adding additional event filter
expressions. Legal filters for an expression are Event, Fixture, or Runner. The conditions to which
these filters are applied are specified by the eventFilterConditional rule.
Our language allows for four different SQL-like conditionals. All of which are self explanatory via
their SQL counterpart, apart from LIN which was created to aid the user in forming compacted
event filters. In SQL it is possible to shorthand the statement:
WHERE runner.name = Chelsea OR runner.name = Arsenal

By using the syntax:


WHERE runner.name IN (Man Utd,West Ham)

Syntactically these two statements are identical however the latter is far simpler to use. Currently no
such equivalent exists for shortening the statement:
WHERE runner.name LIKE Man% OR runner.name LIKE st%Ham%

I have created the conditional LIN as an abbreviation to a hypothetical LIKE IN, this will allow us to
specify in shorthand a union of LIKE statements. An example of a legal event-block item would be:
EVENTS{
EventType = Soccer AND Event LIKE %Barclays% AND Runner = Chelsea;
}

This event block says that the strategy should be applied to all soccer matches where event includes
the text Barclays and Chelsea is playing. Later in my first tree parser, this statement is passed to a
database wrapper and translated into valid SQL. In this instance the sql generated would be as
follows:
SELECT event_type.id, event_type.name, event.id, event.name, fixture.id, fixture.name,
fixture.initialtime_ms, fixture.starttime_ms, fixture.endtime_ms FROM event_type, event,
fixture, fixture_runner, runner WHERE fixture.event_id = event.id AND event.event_type_id =
event_type.id AND fixture_runner.fixture_id = fixture.id AND fixture_runner.runner_id =
runner.id AND event_type.name = 'Soccer' AND event.name LIKE '%Barclays%' AND runner.name =
'Chelsea';

Next a variable statement is considered:

25

Strategy Testing in Betfair Markets

varStat
:
|

ID ';'
ID '=' logicalExpression ';'

A variable is an identifier (Series of alphanumeric characters starting with a letter) which may be
simply defined or assigned the value of a logical expression. Scope is a feature of our language and is
handled automatically by the final tree walker. Therefore any variables defined within this initial
varstat rule will be given global scope.
A logical expression is a pattern which prescribes a series of expression rules ordered by their
operator precedence. We start with a top-level rule called logicalExpression to represent a complete
expression. Rule logicalExpression will match operators with the weakest precedence first and will
refer to a rule that matches sub-expressions for operators with the next highest precedence. The
bottom rule of this pattern will describe expression atoms such as integers or identifiers. Below are
the first three rules of an expression terminated by our bottom rule: value:
logicalExpression
:
booleanAndExpression ( OR booleanAndExpression )*
;
booleanAndExpression
:
equalityExpression ( AND equalityExpression )*
;
equalityExpression
:
relationalExpression ( (EQ|NEQ) relationalExpression)*
;
.
.
.
value
:
INT
|
FLOAT
|
ID
|
betOperator
|
functionCall
;

The bottom level elements of an expression may be an integer, a float, an identifier, bet operator or
functionCall. Each of these will evaluate to a value of type double.
Perhaps a more elegant way to describe the makeup of logical expressions is through a parse tree
diagram. Figure 5 shows the conditional IF(a OR 9 > 3*2){} broken down into its
constituent parts.

26

Strategy Testing in Betfair Markets

Figure 4.1: Parse Tree diagram for the condition "IF (a OR 9 > (3*2)) {}

27

Strategy Testing in Betfair Markets

A strategy block is the most important feature of our language; here we define the rules for applying
our strategy:
strategy
: 'STRAT' '(' stratParam ')' '{' stat* '}'
;
stratParam
: logicalExpression (',' logicalExpression)?
;
stat
:
conditional
|
varStat
|
exit
|
print
|
bet
;

A strategy must contain the text STRAT followed by 1 or 2 logical expressions. The first logical
expression is required and specifies the resolution of data (in seconds) at which we wish to test our
strategies. For example if we wished to apply our strategy against data at 60 second intervals, then
we would pass in a logical expression which evaluates to 60 as our first parameter.
Obviously there is a compromise between resolution and speed. The higher resolution (smaller
value), the more frequent the database will be polled and the longer it will take to evaluate a
strategy over the length of an event. Frequently, this loss in speed will be counteracted with
increased accuracy as the strategy will have more opportunities to match the desired bet.
The second logical expression is optional and specifies the time in minutes (relative to the start of an
event) at which we wish to start testing our strategy. By default this value will be set to the first
timestamp available, which will often be some time before the event has actually started. If we were
only interested in an in-play market then we would specify a value of 0. If we wished to specify
twenty minutes into the event then we would specify a value of 20, just as -20 would entail 20
minutes prior to the start of the event.
The body of a strategy will consist of zero or more statements. Read rule stat as consisting of one of
the five alternatives:

A conditional block
A variable statement
An exit statement
A print statement
A bet

28

Strategy Testing in Betfair Markets

Finally the structure of a function definition is defined:


functionDef
: 'function' ID '(' formalParamList? ')' functionBlock
;
formalParamList
: ID (',' ID)*
;
functionBlock
: '{' stat* returnStat '}'
;
returnStat
: 'return' logicalExpression ';'
;

A function definition consists of the text function followed by an initial identifier to represent the
functions name. The function definition will include an optional list of formal parameters (comma
separated list of identifiers), the values of which will equal the values passed via the function call.
A function block consists of a series of optional statements terminated by a return statement. The
value of the logical expression following the return keyword will be the value returned to the
initial function call.

4.2 AN EXAMPLE SCRIPT


With the main syntax of our language defined we can illustrate a legal betting strategy:
EVENTS{
EventType = Soccer AND Event LIKE %Asian%Cup AND Runner = Japan;
}
oddsMargin = 0.2;
STRAT(60,0){
backPrice = backPrice1(MatchOdds, Japan);
IF(!placed($backBet)){
IF(backPrice > 1.5 AND backPrice < 3){
$backBet = BACK(MatchOdds, Japan, 1), 10);
}
}
ELSE{
layOdds = layPrice1(MatchOdds, Japan);
IF(layOdds < maxLayOdds()){
LAY(MatchOdds, Japan, 12);
exit;
}
}
}
function maxLayOdds(){
changeInOdds = $backBet(odds) * oddsMargin;
maxOdds = $backBet(odds) - changeInOdds;
return maxOdds;
}

29

Strategy Testing in Betfair Markets

The above strategy will be evaluated against all Soccer matches where the event name contains the
string %Asian%Cup%4 and Japan is playing. We should read the strategy as follows:
Execute this strategy at a data resolution of 60 seconds and start at the moment when the event
goes in-play. Every iteration, use the function backPrice1 to query the best back odds available in the
market and store this value in variable backPrice. If we have not yet placed a back bet with label
$backBet and Japans odds are between 1.5 and 3 then place a back to win bet on Japan for 10.
Later if we observe lay odds which are 20% smaller than those stored in the bet referenced by
$backBet then we can place a lay to win bet on Japan for 12. Similar strategies are covered in more
detail in Section 6.

4.3 TREE CONSTRUCTION


A grammar by itself is not particularly useful. By this stage we can ensure the syntax of our language
conforms to our specification. However, we require further processing to do anything exciting! What
we need is a transient data structure to store a correct strategy for later processing. Luckily ANTLR
provides us with the tools to do just that!
Well use the same parser grammar to build an abstract syntax tree (AST) and extend our grammar
with tree construction rules. Once we have translated our syntactically correct grammar into this
intermediate form, we can use a tree parser to walk the AST and execute embedded actions.
Tree construction rules are output alternatives that specify the grammatical, two-dimensional
structure of the tree you want to build from the input tokens. For example, we can extend the
strategy rule to create an AST node as follows:
strategy
:
;

'STRAT' '(' stratParam ')' '{' stat* '}' -> ^(STRAT stratParam stat*)

The symbol -> begins each tree construction rule. The ^ character notifies ANTLR to create a new
AST node and place the text STRAT as tree root. The children on the tree node are then stratParam
and stat* respectively.
Note that many input symbols, such as comma, semicolons, colons, curly brackets, parentheses and
so on, are used solely to indicate structure in our language. These token are superfluous to the
semantics of our strategy and can therefore be omitted from our AST.

4.4 TREE WALKING


The functionality required for our language has several logically separate phases, most of which
require the output of a previous stage in order to be processed. It is just too difficult to translate
from our betting language into Java in one step. For example, a translator must walk the input
program once to get function definitions and a second time to resolve references to those
definitions.
4

The character % represents a wildcard for matching any character(s)

30

Strategy Testing in Betfair Markets

For this reason we have broken down our tree walking into three separate stages, which process,
alter and extract information from the AST. The final stage executes the strategy and emits output
relative to the strategy being executed. In order to carry out these computations on our AST we
must also augment our tree walkers with actions.
Actions are blocks of text written in the target language and enclosed in curly braces. The recognizer
triggers them according to their locations within the grammar. For example, the following rule prints
out the statement I have found an event type! after the parser has processed the
eventFilterStatement rule:
eventFilterStatement
:
^('EventType' STRING
{System.out.println(I have found an event type!);}
(AND eventFilterExpression)*)
;

The action performs a translation but its rather uninteresting as the processing of any event type
will always result with the same message. To perform a more useful translation we could embed our
print statement with actions which refer to the AST input:
eventFilterStatement
:
^('EventType' STRING
{System.out.println(I have found an event type: +$STRING.text+!);}
(AND eventFilterExpression)*)
;

Now, if our filter is provided with the statement: Event Type = Soccer;, our tree walker will output
the message: I have found an event type: Soccer! Much better!

4.4.1 SEMANTIC ANALYSIS: ROUND 1


A desirable property of our language would be the ability to keep variables at different levels of our
program separate from one another. Since there are only a small number of short variable names,
and programmers share habits about the naming of variables (e.g. i for an array index), in any
program of moderate size the same variable name will be used multiple times. Without some
implementation of scope we would require separate variable names for every value we wished to
maintain This would be a pain!
We have implemented a form of dynamic scoping to aid our user in several respects:

The ability to define global variables and access them at all parts of the program
The ability for users to create a local copy of any variable which was defined in a higher
scope.
The ability for users to create a new variable at any scope, whose definition is removed on
exit from that scope.

Our implementation is not truly dynamic in nature as user-defined functions will only have access to
global variables as well as the values of any variables passed into the functions They do not have
access to other variables defined within the strategy block. This feature was purposefully excluded
to prevent unintentional overwriting of key variables in our strategy.

31

Strategy Testing in Betfair Markets

Before any tree walking is done, all code statements in the @members block of our tree grammar
are executed:
@members{
Scope globalScope = new Scope(null);
Scope currentScope = globalScope;
public void pushScope(){
currentScope = new Scope(currentScope);
}
public void popScope(){
currentScope = currentScope.pop();
}
.
.
}

On entering our tree grammar we first initialize a global scope by creating a new Scope object with
parent null. We point our current scope reference to this new object and define two methods to
push, and pop elements respectively from our stack of Scopes data structure. On entrance to a new
code block, be that a strategy, conditional, or function block, a new scope is pushed onto our stack
which is later popped when the block is exited.

4.2: UML for Scope data structure

Connected to each Scope is a reference to a symbol table. In our implementation we have two main
data types, variables and functions (However this could easily be extended to cover additional data
types such as Strings or Arrays). Whenever our tree parser comes across a new variable or function
definition, a lookup method is called within the current Scope object. The stack of scopes is
traversed and the first object with the same name is returned. If no object exists, then a new object

32

Strategy Testing in Betfair Markets

is created with the desired properties. The code exert below illustrates a variable assignment to help
explain this process:
varDef
: ^(VARASSIGN ID logicalExpression)
{
VariableSymbol s = (VariableSymbol)currentScope.lookup($ID.text);
if(s!=null) {
$ID.symbol = s;
}
else if (s==null){ //Variable does not currently exist so create in current scope
s = (VariableSymbol)currentScope.defineVariable($ID.getText());
s.definition = $VARASSIGN;
$ID.symbol = s;
}
}

On encountering input which matches the varDef rule shown above, our tree walker will go through
the following steps:

Step1 Our tree walker will enter the action block denoted by the first pair of curly brackets.
A lookup function is called in the current Scope object which will look for a previously
defined variable with the same name.
Step2- If a variable symbol object exists in either the current scope or in any higher scope
then we store a reference to this symbol in our AST.
Step3- If no record of the variable was found, we create a new record of the symbol in our
current scope, add a reference to the tree node which first defined this variable
($VARASSIGN) and as before store a reference to the newly defined symbol in our AST.

As we are currently undergoing semantic analysis, no value is stored in a newly created variable
symbol. This is left for the final strategy interpreting stage (Section 4.4.3).
Prior to this however, our tree walker will attempt to parse the event Filter rule. Each filter
expression is processed in turn and added to a data structure of filter types. Once all expressions
have been evaluated, the method populateFixtures from class DbWrapper is called which will create
a connection to the database, convert the conditionals into valid sql and populate our framework
with all static information related to our desired events. If no event filter exists then this stage will
be skipped and the tree walker will continue its parse of our strategy.
eventFilterExpression
:
^(FTYPE f=filter eventFilterConditional[$f.text])
;
eventFilterConditional [String f]
:
^('=' STRING) {DbWrapper.addFilter(f, "equals", removeQuotes($STRING.text));}
|
^('LIKE' STRING) {DbWrapper.addFilter(f, "like", removeQuotes($STRING.text));}
|
^('IN' repeatedString[f, "in"])
|
^('LIN' repeatedString[f, "lin"])
;
repeatedString [String f, String ft]
:
^(RS (STRING {DbWrapper.addFilter(f, ft, removeQuotes($STRING.text));})+)
;

Finally as nothing currently connects a function call to its respective definition, the last order of
business is to collect all function call and definition references.

33

Strategy Testing in Betfair Markets

functionCall
@init{FunctionSymbol s = null;}
:
^(FCALL ID // ^(FCALL ID paramList?)
{
s = (FunctionSymbol)currentScope.lookup($ID.text);
if(s != null){ //If function has already been defined
$FCALL.symbol = s; //Store reference of symbol
}
else{ //create dummy table entry
s = (FunctionSymbol)globalScope.defineDummyFunction($ID.text);
$FCALL.symbol = s;
if(isBuiltInMethod($ID.text)){
s.builtInFunction = true;
}
}
paramList?)
;

On entering a function call, the scope is checked for the existence of any function definitions with
the same name. If a FunctionSymbol is returned then we store this reference in our current tree
node In this simple case our function call will now include a reference to its respective definition!
Sadly however, this case requires the definition to occur before the function call which is rare
(although our language does allow for this functionality). If we wish for our language to handle
forward references, then some additional steps are required.
So, if the most likely case occurs (i.e. no function with the same name has yet been defined), we
create a special dummy function within our symbol table. A dummy function should be viewed as a
placeholder for a real function definition. Currently we know the location of the functions call
statement but the location of the function definition itself is still unknown! In order to resolve a
dummy function we must first inspect all function definitions within our strategy.
functionDef
@init{FunctionSymbol s = null; int startStreamIndex=input.index(); pushScope();}
@after{popScope();}
:
^(FDEF ID
{
s = (FunctionSymbol)globalScope.lookup($ID.text);
if(s != null && s.dummy == true){//Dummy symbol has been defined
s.convertDummy(globalScope);//convert to normal function definition
s.definition = $FDEF;
$FDEF.symbol = s;
if(isBuiltInMethod($ID.text)){
s.builtInFunction = true;
}
}
else if(s == null){//define function
s = (FunctionSymbol)globalScope.defineFunction($ID.text);
s.definition = $FDEF;
$FDEF.symbol = s;
if(isBuiltInMethod($ID.text)){
s.builtInFunction = true;
}
}
else{//function has already been defined
addToErrorList("ERROR: function "+$ID.text+" already defined");
}
}

34

Strategy Testing in Betfair Markets

formalParamList[s]? functionBlock)
;

Inside a function definition block, the scope is checked for the existence of any function definitions
with the same name. If a dummy function is returned, we convert the dummy into a normal function
definition and resolve its references as before. If no function definition exists, we create a new
FunctionSymbol object within our symbol table.
Otherwise if a function has already been defined and the dummy flag has not been set then we have
just encountered a duplicate function definition - This will not do! We add a useful error message to
our error list (A list of errors shared by all Tree walkers) and move on.
We have now connected all function calls with their respective definitions but some further error
handling is required. Currently we do not check for the following cases:

A function call exists without a respective definition.


A user has defined a function which has the same name as a built-in function.
The number of parameters passed into a function do not match the number of arguments
specified in the function definition

All three are resolved in the next round of semantic analysis.

4.4.2 SEMANTIC ANALYSIS: ROUND 2


This second tree walker is comparatively much smaller. In fact only one tree grammar rule is walked:
functionCall. It may seem unnecessary to parse the tree an additional time however the overhead is
minimal and further semantic analysis which was not possible in SymbolTreeWalker is required
before our strategy is executed:
functionCall
@init{FunctionSymbol s = null;}
:
^(FCALL ID p=paramList?)
{
s = (FunctionSymbol)$FCALL.symbol; //no need for scope!
if(s != null && !s.dummy && !s.builtInFunction){
int numParam = $p.numParam;
int numArg = s.getNumArguments();
if(numParam != numArg){
addToErrorList(Error1);
}
}
else if(s != null && s.dummy && !s.builtInFunction){
addToErrorList(Error2);
}
else{
addToErrorList(Error3)
}
}
;

Where:

35

Strategy Testing in Betfair Markets

Error1 = ("ERROR: function "+$ID.text+" was called with "+numParam+" parameters


however expects "+numArg+" arguments")
Error2 = ("ERROR: function "+$ID.text+" does not reference a user-defined OR builtin function definition")
Error3 = ("ERROR: user-defined function "+$ID.text+" has the same name as a builtin function")

As you may notice, the need to maintain a stack of Scopes has been eliminated as all references to
our symbol table are now stored within the AST. Now if we wish to check for a function definition we
merely check for a symbol table reference embedded in the grammar rules root node. (The result of
this would make further semantic analysis at this level much more elegant). Our tree walker
processes this rule in the following steps:

Step 1 The function calls root node ($FCALL) is checked for a reference to a function
definition.
Step 2 If the function definition is regular (i.e. exists and is neither a dummy nor built in
function) then the number of parameters passed into the function are checked against the
number of arguments specified by the function definition. If these two numbers do not
match, an error is added to the error list to warn the user of this fact.
Step 3 Else if the function call references a dummy function (i.e. no function definition was
found with the same name as the function call) then an error is added to warn the user.
Step 4 Finally we add an error if the user defined a function which shares the same name
as a built-in function. By disallowing this event, we prevent the user from getting
unexpected results by accidently calling the non-intended function.

With all semantic analysis complete, the strategy will enter the interpreter stage.

4.4.3 STRATEGY INTERPRETER


Our strategy interpreter is only executed if the following two conditions hold true:

The error list contains no errors from our previous tree walking
And our event filter has matched at least one fixture in the database.

If this is not the case then our strategy is not executed and we are provided with a message detailing
why (In the case of a non-empty error list, we are given a print out of all errors to explain why our
strategy is not syntactically or semantically correct). Otherwise, the strategy has made the cut and
will be executed by our final tree walker.
A strategy is applied to each fixture returned from our event filter query and executed recursively,
either until the fixture has concluded or an exit statement is processed. The resolution (in seconds)
of the strategy will be indicated by the first logical expression: l1 and the relative start time (in
minutes) of the strategy will be indicated by the value of the optional second logical expression: l2.
On entering a strategy, a new scope will be pushed onto the stack.

strategy
@init{stratBlockIndex = input.index(); pushScope(); stratBlockCounter++;}
:
^('STRAT' l1=logicalExpression

36

Strategy Testing in Betfair Markets

(l2=logicalExpression
{
if(stratBlockCounter == 1){ //executed on first entry into strategy
fixture.setStrategyTime($l2.value);
}
})?
^(SLIST stat*))
{
popScope();
double seconds = $l1.value;
fixture.incrementTime(seconds);
if(!fixture.isEventFinished){
input.seek(stratBlockIndex);
strategy(); //re-iterate over strategy
}
}
;

By default a strategy will start at the first recorded timestamp in our database, however this can be
overridden by providing a second logical expression. If this is the case, the function setStrategyTime
will be called on the first iteration of our strategy block. This function will query the database for the
timestamp closest to the value provided and override the default start time. The tree walker will
then proceed to walk all statements present within the strategy block.
Once the last statement has been walked, the strategies scope is popped from the stack and the
time of the event is incremented by the number of seconds specified (or as close to as possible
depending on available data). If this new timestamp is greater than the end time of the event, then
the flag isEventFinished is set and the strategy will conclude for this fixture instance. If however, the
event has not concluded, then the tree node pointer is set to the root node of the strategy rule and
the strategy is re-evaluated for the new timestamp of the event. Additional executions which are
carried out in the strategy interpreter stage are as follows:
The flow of a conditional block is determined from its containing expressions. If the aggregate value
of all expressions resolve to 1 then the AST node pointer used by ANTLR to determine the next rule
for execution, is manually adjusted to point to the first statement within the conditional block.
Alternatively, the flow of execution is passed to the next statement after the condition.
On entrance to a function call, the reference contained within the $FCALL root node is used to
determine the next flow of execution. If the function call references a user-defined function then the
AST node pointer is adjusted to point to this rule. Otherwise the function call will reference a built-in
function and the Java reflection discussed in Section 5.1.4 is utilized to dynamically call the
respective built in function.
On entering the bet rule, the values for market, selection and stake are first evaluated. If any of
these parameters utilise a run-time placeholder such as the selection placeholder Home then these
will be translated to their intended string values by the interpreter. If all values supplied to the bet
statement are legal for the current fixture then a new bet is placed by our testing engine (Section
5.1.2). Otherwise, if one or more values are not legal with respect to the current fixture (i.e. the
fixture does not contain the specified market) then an error is outputted to the user and the strategy
is applied to the next fixture. Table 4.3 shows the list of all built-in functions provided by our
language:

37

Strategy Testing in Betfair Markets

Function(Arguments)
placed(<betlabel>)
minute()
start()
end()
backPrice1(<market>, <selection>)
backPrice2(<market>, <selection>)
backPrice3(<market>, <selection>)
layPrice1(<market>, <selection>)
layPrice2(<market>, <selection>)
layPrice3(<market>, <selection>)
backVolume1(<market>,<selection>)
backVolume2(<market>,<selection>)
backVolume3(<market>,<selection>)
layVolume1(<market>,<selection>)
layVolume2(<market>,<selection>)
layVolume3(<market>,<selection>)
lastPriceMatched(<market>, <selection>)
profitBeforeComission(<market>, <selection>)
profitAfterComission(<market>, <selection>)
comission(<market>, <selection>)
favourite(n)
goal(<selection>)

set(<selection>)

Description
Returns true if the bet labeled <betlabel> has
been placed, false otherwise.
Returns the current minute of the fixture.
Returns the first minute of the fixture relative to
its in-play time.
Returns the last minute of the fixture relative to
its in-play time.
Returns the best price for backing
the specified selection.
Returns the second best price for backing
the specified selection.
Returns the third best price for backing
the specified selection.
Returns the best price for laying
the specified selection.
Returns the second best price for laying
the specified selection.
Returns the third best price for laying
the specified selection.
Returns the money available to back the
specified selection at the best price.
Returns the money available to back the
specified selection at the second best price.
Returns the money available to back the
specified selection at the third best price.
Returns the money available to lay the
specified selection at the best price.
Returns the money available to lay the
specified selection at the second best price.
Returns the money available to lay the
specified selection at the third best price.
Returns the last price matched on the
specified selection.
Returns profit for the market if the
selection were to win.
Returns profit minus commission for the
market if the selection were to win.
Returns commission for the market
if the selection were to win.
Returns an index for the favourite nth runner
Returns true if a goal was made by <selection>
since the current score of the match was last
queried. <selection> is optional
Returns true if a set was made by <selection>
since the current score of the match was last
queried. <selection> is optional

4.3: Table of Built-In Functions

38

Strategy Testing in Betfair Markets

4.5 SUMMARY
Our language provides some novel features for forming market independent betting strategies. The
full set of features is as follows:

A basic event filtering language is developed, allowing us to specify an event filter at several
levels of complexity.
Several stages of semantic analysis are carried out prior to executing a strategy. We are
provided with useful error statements if our strategy is semantically incorrect.
We are given the freedom to specify the resolution of data and relative start time at which
to evaluate our strategies.
A function library is provided which enables us to query the current state of the market. Our
implementation is completely separate from our language grammar to allow for additional
functions to be added easily.
Sophisticated conditional statements can be formed to control the flow of our strategies.
Variables and User-defined functions enable us to form complex expressions.
Dynamic scoping is utilized to carry variables across different code blocks as well as strategy
iterations
Exit statements allow us to terminate a strategy as soon as the ideal market position is
reached.
Print statements allow us to monitor the execution of our strategy. Additionally these may
be used to print additional information on which to refine our strategies.
Back and lay bets may be placed and optionally assigned a label. All components of a bet are
maintained by our testing engine. Useful properties such as bet stakes, potential profit and
liability may be queried at runtime and utilised as expressions.
Useful in-play events may be inferred from the price data of our events. For example our
implementation may infer the occurrence of a goal or winning of a set.

The next section of this report will detail the steps made to implement a framework to evaluate such
strategies against historical data

39

Strategy Testing in Betfair Markets

5 EVALUATING STRATEGIES
This chapter discusses the implementation of a framework for evaluating betting strategies. By
executing these strategies against fully time stamped historical data, we can output an obvious
measure for a strategies performance profit.

5.1 METHODOLOGY
5.1.1 FRAMEWORK DESIGN
Figure 5.1 is a class diagram for our testing application. As can be seen, the highest level object is a

fixture which contains all non market-specific information regarding an event. For each event
returned by our event filter, a corresponding fixture object is created.
Associated with every fixture is a collection of Bet objects. Whenever a new bet is successfully
placed, a reference to this bet is added to the current fixture. Later if we wish to query the
parameters of a previous bet, we can use our fixture class to do so. The process of placing a bet is
discussed in section 4.4.3.
Also associated is a collection of runners and Market objects. The runner collection is the list of all
runners taking place in the fixture, for example, the list of all horses competing in a horse race
(ordered by their stall number). A Market object is a class representing all betting markets available
to the current fixture. A football fixture will have several markets - a Match Odds market, a Correct
Score market as well as others.
Each Market will maintain a running total of bets placed as well as the current profit (or loss!)
incurred on the market. Each Market object is composed of several selections, each represented by
a Selection object. A Match odds market will have three associated Selection objects One for the
home win, one for the away win and a third for the draw.
A Selection object also, will maintain a running total of bets and profit incurred. A field of type
Boolean is set to true if the selection was the winning selection in the market. A winning selection is
inferred from the market data as described in section 5.1.5.

40

Strategy Testing in Betfair Markets

Figure 5.1: UML class diagram for testing application

41

Strategy Testing in Betfair Markets

5.1.2 PLACING BETS


To place a bet, our language interpreter will first evaluate the components of the bet grammar rule
(see bet grammar rule in section 4.1). If all components are legal at runtime, a new bet object is
created with the bet components passed in as parameters. Next, the tree walker will attempt to
place the bet with market. If the market is active at the current time, then the bet is placed at the
stake specified and on the best market odds available. Once placed, the Boolean field placed is set
to true, various bet counters are incremented and the expected profit and liability for the fixed odds
bet are calculated and stored within the Bet object. As the winning selection has already been preevaluated in our database [see section inferring event data], we can also update the current running
profit for our strategy. Lastly, a reference to the Bet object is added to the current fixture.
If however the market is suspended at the current time, then the bet is not placed successfully, the
user is notified that no bet was placed due this suspension in the market and a reference to the bet
object is not added to the current fixture.

5.1.3 LOW INITIAL OVERHEAD, SMALL MEMORY STORAGE, QUICK SQL


When designing our framework the main design decision considered was as follows: Do we:

A Store all dynamic price data for our desired events in an additional in-memory database
B Store only the static information and poll the database when we require price data.

Option A would require a large overhead to poll the database in order to store all price data locally.
This overhead will occur prior to executing any strategies, will increase greatly with the number of
fixtures specified by our event filter and would have no correlation with the complexity of a strategy.
For example the strategy:
EVENT{
EventType = Soccer;
}
STRAT(60){
BACK(MatchOdds, Home, 10);
exit;
}

Would require the same large overhead as a strategy utilizing many complex conditionals blocks,
user defined functions and built-in methods which rely on Java reflection. Additionally, all price data
for a particular timestamp will only be read once and can be considered redundant once the current
iteration for our strategy has been incremented.
On the plus side, if the time to populate our in-memory database could be optimised then the
processing of a strategy can be carried out very efficiently. This could have further benefits as the
evaluation of a strategy from a remote location would cease to be dependent on an internet
connection once the initial overhead was complete.
Option B would entail a very small initial overhead with the weight of execution time shifted towards
the complexity of the strategies tested. When polling our database we extract only the information
which is static to an event. For example, the start time of a fixture or the unique database identifier

42

Strategy Testing in Betfair Markets

of a selection will remain constant over the length of an event. By only storing this static information,
we can keep our initial overhead and memory requirements to a minimum whilst storing enough
information to form fast SQL queries for processing our database. Additionally, the underlying
concept of processing information only when we need it, would translate well if we decided to
extend our framework to execute strategies against live market data (Discussed in section 0).
A downside of option B would be the need to poll the database at regular but short intervals,
reducing the speed at which strategies can be executed. This however can be optimised. For
example, for popular SQL queries we can produce substantial speed increases by converting the
queries into stored procedures and thus compiling the query only once at server-side. For
particularly slow or cumbersome queries which act on large data sets, we can create database
indexes to improve the speed of data retrieval.

5.1.4 JAVA REFLECTION FOR BUILT-IN FUNCTIONS


When processing a function call, we can quickly determine whether the rule references a userdefined or built-in function by checking a flag in its respective symbol table definition. (This is made
possible by some pre-processing in the first semantic analysis stage see section 4.4.1).If the
function call references a user-defined function then we can extract the index of the function
definitions root node and alter the flow of the tree walker to immediately process this rule This is
equivalent to a method call.
If however we determine that the function call references a built in method then we only have the
function name and its respective parameters to go by! The obvious implementation is a switch
statement on all function names but this would entail an additional change to our grammar every
time we wished to extend our function library. A better more dynamic implementation uses Java
reflection.
Reflection allows us to examine or modify the runtime behaviour of all classes, methods and fields
running in the JVM. Simply knowing a functions name, class and parameters is sufficient to invoke
the method dynamically.
The class FunctionLibrary contains the set of all built-in methods available to a strategy. Within this
class is the following top-level method:
Object executeMethod(String methodName, Class paramTypes[], Object paramList[]){
Object retObject = null;
try {
Class<FunctionLibrary> thisClass = FunctionLibrary.class;
Object inst = thisClass.newInstance();
Method fn = thisClass.getDeclaredMethod(methodName, paramTypes);
retObject = fn.invoke(inst, paramList); //call the method
}
catch (Throwable e) {
System.err.println(e);
}
return retObject;
}

43

Strategy Testing in Betfair Markets

At runtime, any function that references a built-in method will call the method above. A new
reference to the desired method is created and in-turn used to invoke the function.
Reflection is an elegant solution to our problem however does contain some drawbacks. One is a
performance issue. Reflection will always be much slower than direct code when used for field and
method access. The extent of this will depend on how reflection is used in a program. For our
language the use of reflection is not used in any performance-critical logic and should therefore be
suitable for this task.

5.1.5 INFERRING EVENT DATA


In order to extend our language with the ability to evaluate bets and react to events that occurred
within the fixture (i.e. a goal being scored in a football fixture), we have attempted to infer any
useful events solely from the market price data.
To evaluate the outcome of a bet we must first know which selections were concluded as winners
and which as losers. We can infer a winning selection by analyzing the LPM (last price matched) of
each in the final few seconds of the event. For example, a selection which is receiving matched back
bets of odds 1.01 in the final concluding seconds of an event can be inferred with high accuracy to
be a winning selection. For all markets, the selection with the lowest LPM at the end of the market is
defined as the winner. Through deduction we set all non-winning selections as losers.
This problem is more complicated for a To Be Placed horse racing market. Here, a winning
selection could be any horse which finished within the first n positions, where n is defined in the
market rules. As Fracsoft does not carry this information, the winning selection in any To Be
Placed market has been defined as the first horse to fulfil the criteria discussed above.
We can additionally infer the current score of a football match or current set points in a tennis
match by analysing the Correct Score and Set Betting markets respectively. We will use the
example of a Correct Score market to explain this feature.
Each selection in a Correct Score market will represent a different final score for the football
match. Selections will range from 0 0, 1 0, 0 1, all the way up to 3 - 3 and Any Unquoted.
Because this is an in-play market, the makeup of odds offered will always be a reflection on the
current state of the match.
When placing a bet with Betfair, a user may specify odds ranging from 1.01 till 1000. Odds of 1.01
will reflect a 99% chance of the outcome occurring5. Odds of 1000 on the other hand will reflect a
0.001% probability. When applied to the Correct Score market, odds of 1.01 are normally offered
on the current score when there is only one minute remaining in the match. Odds of 1000 will
always be placed on all outcomes which are no longer feasible, for example the 0 0 outcome on
a match which is currently 1 0.
By analysing at runtime the smallest score which does not offer odds of 1000, we can accurately
infer the current score of the match. We have abstracted this notion further in order to determine

That is, as predicted by the market

44

Strategy Testing in Betfair Markets

whether a goal has been scored, we can do so by comparing the current score with the last score
recorded.
This same functionality can be applied to Tennis although unfortunately, a Horse Racing in-play
market is too short lived and lacks the occurrence of well-defined events. Therefore our
implementation does not attempt to infer the current position of all horses in a race. Instead, the
user is given the functionality to query the current market predicted favourites.

5.2 DATABASE
To test our strategies, we require a database containing historical price data for a suitable number of
events. The data is held within a PostgreSQL database to which our program connects using the JDBC
database drivers. This section describes the design of our database as well as the challenges
encountered in porting data from Fracsoft.

5.2.1 DESIGN

Figure 5.2 is a UML diagram for our database. The database has been designed to be non-sport

specific and can be used to store price data for any event type offered by Fracsoft. This feature is of
key importance if we were to later extend our language with further event types.
Some brief notes about the design:

45

Strategy Testing in Betfair Markets

The starting point in our database is the event_type table. This is the top-level of any sport and
illustrates the type of sporting event taking place. An example of this could be a Soccer, Tennis or
Horse Racing event. An event describes the competition or the sporting occasion. It is the level
directly above an actual fixture. Examples of an event are: Euro 2008 Qualifiers, Monte Carlo
Masters Third Round Matches or IRE Curragh.
A fixture is the main event, the outcomes of which are bet on using the many betting markets. Every
fixture will have a name, such as Arsenal v Chelsea or Federer v Nedal. An initial time which is
the first timestamp at which a bet is recorded. And a start and end time which depicts the
timestamps at which the event goes into play and is concluded respectively. Every fixture will have
an associated set of runners taking part. In a football fixture this will include all teams playing in the
match, in a horse racing fixture this table will include all horses running (ordered by their stall
number).
The market table hosts the names of all betting markets associated with our fixture. A football
fixture will have a Match Odds, Correct Score and Under/Over 2.5 Goals market as well as
others. Although not necessary for the current functionality of our language, we have also included
the respective Betfair market ids. These would be useful if we extended our language to use the
Betfair API.
The selection table will list all the selections available in a betting market. A football Match Odds
market will have selections for a Home win, Away win or alternatively a Draw. A horse racing market
will compose solely of the horses taking part in the race. The column winner is set tot if the
selection was inferred as the winning selection and f otherwise. Again we have included the
respective Betfair selection ids.
Finally the selection_state table holds all price data for a selection. Each row will denote a snapshot
in time for a particular betting market. The market_status column is used to determine whether the
market is currently suspended or active. Inplay_delay shows the delay in seconds between the
betting market and the actual event. There are also three back prices, lay prices, back volumes and
lay volumes per record. This reflects the market display of the Betfair website (Figure 2.1). Finally
total_matched is the total amount matched on the selection at the given timestamp and
last_price_matched shows the last back price at which a bet was matched.

46

Strategy Testing in Betfair Markets

Figure 5.2: Application Database Diagram

47

Strategy Testing in Betfair Markets

5.2.2 CHALLENGES IN PORTING FRACSOFT DATA


Some significant work was required to manipulate the data stored by Fracsoft into a format which is
both sport independent as well as natural from a data retrieval point of view. The following
describes the main challenges encountered in porting the market price data into a model capable of
representing any sport offered by Fracsoft.
The data which Fracsoft provides to its customers is supplied as an XML file. The first 5 or so lines of
this file provide information regarding the fixture such as an event tree, start time, date and market
type. Below this, each line will represent a snapshot of the betting market at a particular timestamp.
Figure 5.3 shows a small excerpt from one of these xml files.

Figure 5.3: Excerpt from Fracsoft data

As you can see, each row is ordered by both selection name and timestamp. The market above is a
match odds market and so every three rows will represent an increment in time. The first problem in
converting this data into a database model was in acquiring a single hierarchy which could be
applied to any event type. Figure 5.4 shows the event tree provided by Fracsoft for a Horse Racing,
Soccer and Tennis match.

Figure 5.4: Different event depths of sports

Notice that each of these trees has a different depth making it hard to develop a data structure to
hold all three. This problem was rectified by creating an event hierarchy equal to the smallest depth

48

Strategy Testing in Betfair Markets

of any event type offered by Fracsoft and joining any data with a greater depth. This ensures that all
events can be represented without any loss in data integrity. As can be seen from our database, the
final hierarchy decided was a depth of three: event type, event and fixture. As the rule for breaking
up an event hierarchy is sport specific, a unique regular expression is required for each.
Figure 5.5 shows the regular expressions used in our database porting script to separate the event

hierarchy for soccer, horse racing and tennis matches.

Figure 5.5: Regular expressions used to separate event hierarchy

A further problem was that the data offered by Fracsoft is often inconsistent. For example a horse
name will sometimes include its stall number and sometimes not. A fixture name will also
occasionally contain irregular characters such as @ or % within its title. Additional regular
expressions are required to filter out these irregularities in order to make all data consistent.
Finally, Figure 5.3 shows that the market price data is ordered on a per-market basis and
consequently will repeat much of the same information. To illustrate this, a horse racing market with
10 horses will have ten records for every timestamp. If we were to store this data at a resolution of
one second in our database, then we would require over 400,000 records solely for this market. This
would entail a considerable degree of redundancy if we included columns for both runner name and
selection id. To reduce this redundancy, all static data has been taken out of the market price data
and placed in an additional hierarchy table: selection.

49

Strategy Testing in Betfair Markets

6 10 STRATEGIES TESTED
This chapter will explain and evaluate ten different strategies at increasing levels of complexity. Each
strategy will attempt to build on from the previous as well as demonstrate an additional feature of
our language.
The first eight strategies will be applied to football markets but will largely contain features which
can be applied to any event type. The final two strategies will be applied to tennis and horseracing
markets respectively.
We have a limited budget to spend on historical market data and therefore a decision was made on
how to best split the funds. We decided it was necessary to place the bulk of this budget in one sport
in order to draw fairly accurate conclusions from our evaluated strategies. Football was chosen due
to the increased number of betting markets; this will make it easier to demonstrate the cross-market
capabilities of our language.
We will start with the most basic strategy that our language can represent: As soon as the football
match goes in-play, back the home team to win for 10 and then exit.
File: footballStrat1.txt
//Back home team at the start of the match
EVENTS{
EventType = 'Soccer';
}
STRAT(60,0){
BACK(MatchOdds, Home, 10);
exit;
}

Running this strategy through our testing engine will place 39 bets on 90 football fixtures6. The
results are as follows:
Statistics for this strategy are as follows:
Fixtures tested: 90
Total bets placed: 39
Total winning bets placed: 19
Percentage of bets successful: 48.72%
Total stake: 390.0
Average stake: 10.0
Profit before commission: 35.6
Profit after commission: 23.8
Percentage return before commission: 9.12%
Percentage return after commission: 6.11%

Football data provided by Fracsoft will seldom contain the full makeup of markets offered by Betfair. Out of the 90 football
fixtures purchased from Fracsoft, 39 of these contain data for the Match Odds market.

50

Strategy Testing in Betfair Markets

This strategy makes a small profit! The default commission of 5% eats up a lot of it though and a 6%
return is nothing to get excited about. Its also interesting that we can win less than 50% of our bets
and still return a profit. (Remember that every bet is placed at different odds and so a single
successful bet can easily outweigh several unsuccessful bets).
Let us refine our strategy by instead placing a bet on the market favourite. If a betting market is a
good indicator of an events outcome then we should expect the percentage of successful bets to rise.
However, as stated above, this may not necessarily entail a larger profit, especially as the favourite
selection will have the lowest odds.
File: footballStrat2.txt
EVENTS{
EventType = 'Soccer';
}
STRAT(60,0){
BACK(MatchOdds, Favourite(MatchOdds, 1), 10);
exit;
}

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 90
Total bets placed: 39.0
Total winning bets placed: 29.0
Percentage of bets successful: 74.36%
Total stake: 390.0
Average stake: 10.0
Profit before commission: 149.5
Profit after commission: 137.0
Percentage return before commission: 38.33%
Percentage return after commission: 35.13%

This is a big improvement! With a larger sample of test data, we could hypothesise that the market
favourite is undervalued at the start of the match.
This is only our second attempt at a strategy so perhaps theres still room for improvement.
Currently we place a bet on every matched odd market. We can restrict these markets by placing a
condition around our bet. The following strategy will only place a bet if the favourite selection has
odds between 1 and 1.7. This should filter out the lesser favourites.

51

Strategy Testing in Betfair Markets

File: footballStrat3.txt
EVENTS{
EventType = 'Soccer';
}
STRAT(60,0){
backPrice = backPrice1(MatchOdds, Favourite(MatchOdds, 1));
IF(backPrice > 1 AND backPrice < 1.7){
BACK(MatchOdds, Favourite(MatchOdds, 1), 10);
}
exit;
}

Gives the output:


Fixtures tested: 90
Total bets placed: 14.0
Total winning bets placed: 11.0
Percentage of bets successful: 78.57%
Total stake: 140.0
Average stake: 10.0
Profit before commission: 12.6
Profit after commission: 10.5
Percentage return before commission: 9.00%
Percentage return after commission: 7.48%

As expected, we have obtained a slight increase in the percentage of successful bets but at a huge
cost in profit. Let us apply the same strategy again but instead require that the favourite runner has
odds between 1.5 and 3. We should now be restricting the heavy favourites.
File: footballStrat4.txt
EVENTS{
EventType = 'Soccer';
}
STRAT(60,0){
backPrice = backPrice1(MatchOdds, Favourite(MatchOdds, 1));
IF(backPrice > 1.5 AND backPrice < 3){
BACK(MatchOdds, Favourite(MatchOdds, 1), 10);
}
exit;
}

52

Strategy Testing in Betfair Markets

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 90
Total bets placed: 29.0
Total winning bets placed: 20.0
Percentage of bets successful: 68.97%
Total stake: 290.0
Average stake: 10.0
Profit before commission: 129.2
Profit after commission: 118.2
Percentage return before commission: 44.55%
Percentage return after commission: 40.77%

A percentage return of 44.55% makes this a very attractive strategy. The next strategy will
demonstrate the cross-market capabilities of our language. If the favourite runner does not meet
our first condition then we will back the 1 - 1 final score in the Correct Score market.
File: footballStrat5.txt
EVENTS{
EventType = 'Soccer';
}
STRAT(60,0){
backPrice = backPrice1(MatchOdds, Favourite(MatchOdds, 1));
IF(backPrice > 1.5 AND backPrice < 3){
BACK(MatchOdds, Favourite(MatchOdds, 1), 10);
}
ELSE{
BACK(CorrectScore, 1 1, 10);
}
exit;
}

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 90
Total bets placed: 31.0
Total winning bets placed: 20.0
Percentage of bets successful: 64.51%
Total stake: 310.0
Average stake: 10.0
Profit before commission: 109.2
Profit after commission: 98.2
Percentage return before commission: 35.23%
Percentage return after commission: 31.69%

Our new strategy places an additional two bets but neither are successful; this pulls our percentage
return down to 35.23%. Let us replace our second bet with something more complex.

53

Strategy Testing in Betfair Markets

As before, we place a bet on the favourite runner if the odds are between 1.5 and 3. However, to
ensure that we only place this bet once per fixture, we must use the built-in method placed(<bet
label>). This method will check a list of placed bets within the current fixture, if a label is found with
the same name then a true condition is returned. If at any point we manage to place a back bet on
the favourite runner then the strategies flow will instead enter the ELSE block.
Here we make use of another useful feature. As discussed in Section 5.1.5, the method
goal(<selection>) will infer the current score from the market price data. If the inferred score has
changed then the selection which scored the most recent goal is determined. In this instance we
want to know whether the runner in $backBet was responsible for this goal. Therefore, we can use
the bet operator: <bet label>(selection) to return this value. A similar trick is used to place a lay bet.
File: footballStrat6.txt
EVENTS{
EventType = 'Soccer';
}
STRAT(60,0){
backPrice = backPrice1(MatchOdds, Favourite(MatchOdds, 1));
IF(!placed($backBet)){
IF(backPrice > 1.5 AND backPrice < 3){
$backBet = BACK(MatchOdds, Favourite(MatchOdds, 1), 10);
}
}
ELSE{
IF(goal($backBet(selection)){
LAY(MatchOdds, $backBet(selection), $backBet(stake)+2);
exit;
}
}
}

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 90
Total bets placed: 48.0
Total winning bets placed: 26.0
Percentage of bets successful: 54.17%
Total stake: 504.0
Average stake: 10.5
Profit before commission: -849.3
Profit after commission: -862.2
Percentage return before commission: -168.52%
Percentage return after commission: -171.07%

This strategy turns out to be our least successful yet. We attempt to improve on this in the next
example.
Here we will demonstrate the feature to create user-defined functions. In this instance, we have
created a function which returns odds 20% smaller than those used in $backBet. If we encounter lay

54

Strategy Testing in Betfair Markets

odds which are smaller than this value then we will have discovered an opportunity to arbitrage our
initial bet.
File: footballStrat7.txt
EVENTS{
EventType = 'Soccer';
}
oddsMargin = 0.2;
STRAT(60,0){
backPrice = backPrice1(MatchOdds, Favourite(MatchOdds, 1));
IF(!placed($backBet)){
IF(backPrice > 1.5 AND backPrice < 3){
$backBet = BACK(MatchOdds, Favourite(MatchOdds, 1), 10);
}
}
ELSE{
layOdds = layPrice1(MatchOdds, $backBet(selection));
IF(layOdds < maxLayOdds()){
LAY(MatchOdds, $backBet(selection), $backBet(stake)+2);
exit;
}
}
}
function maxLayOdds(){
changeInOdds = $backBet(odds) * oddsMargin;
maxOdds = $backBet(odds) - changeInOdds;
return maxOdds;
}

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 90
Total bets placed: 49.0
Total winning bets placed: 26.0
Percentage of bets successful: 53.06%
Total stake: 516.0
Average stake: 10.53
Profit before commission: 99.8
Profit after commission: 86.9
Percentage return before commission: 19.35%
Percentage return after commission: 16.86%

This is a much better strategy than the previous one. This strategy has a smaller return than
footballStrat4 but will compensate with a significant reduction in risk. With a larger sample of test
data, I would hypothesise we would achieve a far greater return than that produced by
footballStrat4.
The last three strategies in this section represent the three examples discussed in Section 3. Our
football example is shown below:

55

Strategy Testing in Betfair Markets

File: footballExample.txt
EVENTS{
EventType = 'Soccer' AND Runner = Chelsea AND Runner = Arsenal;
}
STRAT(60,0){
IF(minute() == 0){
IF(backPrice1(MatchOdds, 'Arsenal') > 3.2){
BACK(MatchOdds, 'Arsenal', 50);
}
}
IF(minute() < 40){
IF(goal('Chelsea')){
BACK(MatchOdds, Draw, 50);
}
}
IF(minute() >= (end()-10)){
chelseaOdds = backPrice1(MatchOdds, 'Chelsea');
arsenalOdds = backPrice1(MatchOdds, 'Arsenal');
IF(chelseaOdds < arsenalOdds){
BACK(CorrectScore, CurrentScore, 100);
exit;
}
}
}

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 1
Total bets placed: 2.0
Total winning bets placed: 1.0
Percentage of bets successful: 50.00%
Total stake: 150.0
Average stake: 75.0
Profit before commission: -12.0
Profit after commission: -13.9
Percentage return before commission: -8.00%
Percentage return after commission: -9.26%

The statistics show that we have only one football fixture in our database which matches the event
filter. The selection placeholder CurrentScore will attempt to infer the current score of the match
from the market price data. To help us analyse a strategy, the testing engine will output information
regarding each bet placed:
Current Fixture being tested: English Soccer Barclays Premiership 2007, Chelsea v Arsenal
Placing bet: BACK(Match Odds, Arsenal, 50.0) at Odds: 3.65, Minute: 0, WinningBet: false
Placing bet: BACK(Correct Score, 2 - 1, 100.0) at Odds: 1.38, Minute: 107, WinningBet: true

The above output shows that two bets were successfully placed, fulfilling our first and third
conditions respectively. Our winning bet can be attributed to backing the current score at the 107th

56

Strategy Testing in Betfair Markets

minute in the correct score market. We can determine therefore that the final score of the match
was 2 1 to Chelsea7.
Our horse racing example:
File: horseExample.txt
EVENTS{
EventType = 'Horse Racing' AND Fixture LIKE '%Ascot%';
}
STRAT(60, 0){
IF(minute() == 0 AND !placed($backBet)){
backPrice = backPrice1(ToBePlaced, Favourite(ToBePlaced, 2));
IF(backPrice > 3){
$backBet = BACK(ToBePlaced, Favourite(ToBePlaced, 2), 100);
}
ELSE{
$backBet = BACK(ToBePlaced, Favourite(ToBePlaced, 1), 100);
}
}
IF(minute() > end()-1){
BACK(ToBePlaced, Favourite(ToBePlaced, 1), 25);
exit;
}
}

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 3
Total bets placed: 4.0
Total winning bets placed: 1.0
Percentage of bets successful: 25.00%
Total stake: 250.0
Average stake: 62.5
Profit before commission: -63.0
Profit after commission: -67.4
Percentage return before commission: -25.20%
Percentage return after commission: -26.94%

Confirmed by http://www.premierleague.com/page/FixturesResults/0,,12306,00.html

57

Strategy Testing in Betfair Markets

Finally, our tennis example:


File: tennisExample.txt
EVENTS{
EventType = 'Tennis' AND Runner = 'Nadal';
}
oddsMargin = 0.2;
STRAT(60,0){
backOdds = backPrice1(MatchOdds, 'Nadal');
layOdds = layPrice1(MatchOdds, 'Nadal');
IF(minute() < 10){
IF(!placed($backBet) && backOdds > 1.5){
$backBet = BACK(MatchOdds, 'Nadal', 100);
}
}
ELSE{
IF(placed($backBet)){
IF(layOdds < maxLayOdds()){
LAY(MatchOdds, 'Nadal', 120);
exit;
}
}
}
}
//place bet if we obtain lay odds which are 20% less than our original back odds
function maxLayOdds(){
changeInOdds = $backBet(odds) * oddsMargin;
maxOdds = $backBet(odds) - desiredOdds;
return maxOdds;
}

Gives the output:


Statistics for this strategy are as follows:
Fixtures tested: 3
Total bets placed: 2.0
Total winning bets placed: 1.0
Percentage of bets successful: 50.00%
Total stake: 220.0
Average stake: 110.0
Profit before commission: 36.4
Profit after commission: 29.9
Percentage return before commission: 16.55%
Percentage return after commission: 13.59%

Due to our small sample of test data all results should be taken with a pinch of salt. However, they
should illustrate how a more comprehensive testing engine will help us to evaluate and refine the
strategies that our language can represent.

58

Strategy Testing in Betfair Markets

7 DISCUSSION AND FUTURE WORK


The betting language and testing application discussed in the previous chapters allow for the
specification of reasonably sophisticated market independent strategies. However, there are still
plenty of features that can be improved upon. The following section will discuss the merits and
limitations of the project.

7.1 SCRIPTING LANGUAGE


7.1.1 IMPLEMENTING TYPES
Section 4.4.1 describes how our language utilizes a symbol table to maintain two different data types:
variables and functions. Currently both are restricted to perform operations on values of type
double. The ability to operate on other types such as strings or long Integers is not fundamental to a
betting language however their addition would simplify some features. Below is an example why:
Currently if we wish to store the favourite runner at the beginning of a horse race we can use the
following assignment:
favRunner = favourite();

This statement will query the database for the current favourite runner and store an index to this
value in variable favRunner. Later if we wished to place a back bet on this same runner, we would
need to do the following:
BACK(Market, Runner(favRunner), 10);

Here, the function Runner will convert the index stored in favRunner into a String representing the
runners name before placing the bet. If our language could store values of type String, then we
could bypass this conversion and instead do the following:
BACK(Market, favRunner, 10);

This feature was excluded from our language to relinquish the user from dealing with additional
types, this followed the belief that a language with more types would be harder to learn. The
implementation of further types would increase the power of our language but at a cost of
increasing our languages learning curve.

7.1.2 INCORPORATING FORM STATISTICS


A runners past performance will be reflected in their odds, the degree by which this is true will
depend on the efficiency of the betting market. Our language has been designed to query the
movement of price data as well as any inferred events. A better language would also utilize form
statistics to query the track record of the runners in a particular fixture.

59

Strategy Testing in Betfair Markets

For example, if a popular tennis player has previously underperformed against a comparatively
unknown opponent, then the market may overestimate the former players performance. A
language which could utilize this information would allow us to design better strategies for similar
situations.
Another good strategy might include the backing of any horse with a five race winning streak. By
extending our Event Filter with an option to extract form statistics, we could create additional
functions for querying such events. For example the function: WinningStreak(Selection, 5) could test
if the runner denoted by Selection has won five or more of his previous races.

7.1.3 ABILITY TO MIX AND MATCH STRATEGIES


Any strategy components we wish to implement must be self-contained within a single STRAT block.
This allows us to readily see the makeup of a strategy but makes it harder to determine which
components are generating the most profit.
Perhaps a more natural way to form a strategy would be to separate its components into separate
blocks. For example a strategy which places a single back bet on the favourite runner could be
combined with another with aims to hedge risk.
When testing a strategy we could try to mix and match these individual components to determine
which combinations produced the best results. Additionally we could extend our test suite to
automate this functionality i.e. apply all different combinations of strategies against the same set of
test data. The test suite could provide statistics for each and notify the user of the best performing
combinations. These could then be optimised and re-tested to form more optimal betting strategies.

7.1.4 ADDITIONAL ERROR HANDLING


The parser generated by ANTLR is not particularly user-friendly when it comes to syntax errors.
Currently, if a strategy contains a syntax error, the testing application will provide a generic error
message before printing out a stack trace and terminating execution. It is possible to extend the
error handling provided by ANTLR in order to provide more informative error reporting. This should
be employed for a more professional application.

7.2 TESTING APPLICATION


The testing application performs as intended. It takes a strategy as input and carries out syntactic
and semantic analysis. If no errors are present, then the strategy is applied against each fixture
which fulfils the user-specified criteria. Events such as goals are inferred at run time and bets are
placed when appropriate. At the end of execution, statistics are provided which evaluate the
performance of the strategy.

7.2.1 GUI
As a command line tool which takes a strategy as input, the application may be difficult to use by
those with limited computer experience. For this software to appeal to a larger market, it could be
practical to write a GUI.

60

Strategy Testing in Betfair Markets

Some possible features for a GUI could include a text editor to create strategies, a graphical interface
for syntactic and semantic analysis and a tree structure of possible events to simplify the selection of
event criteria.

7.2.2 Separate Function Libraries for Different Event Types


Currently a common library of built-in functions is shared by all event types. I.e. A horse racing
strategy will have access to all football-specific built-in functions. Therefore it is the users
responsibility to only use the built-in functions which are applicable to the event type specified by
the strategy.
A better implementation would include a common function library which contained all non-sport
specific functions as well as several sport-specific libraries. Additionally any reference to a built-in
function which conflicts with the event filter should be processed during the semantic analysis
stages and highlighted as an error.

7.3 DATA
7.3.1 IMPLEMENT EVENT DATA
The availability of event data would relinquish our current need to infer events (see section 5.1.5).
As is there is a chance that the testing suite will place a bet in reaction to an event which never
occurred. Due to this fact, the results obtained when evaluating a strategy should be taken with a
pinch of salt. In theory, if fully time-stamped event data was available, it would be possible to
produce extremely accurate strategy evaluations.

7.4 ANTLR
7.4.1 EXTEND TREE PARSER
The main argument against using a generated tree parser concerns performance; the results are
often slow as well as memory intensive.
In our application, semantic analysis is carried out in sub-second speed and the time-consuming logic
of our interpreter is spent querying our database. Therefore for the scope of this project, this speed
decrease is not a major concern.
However, our grammar contains two rules which have the potential for deep recursion. The rule
strategy will be executed recursively until the fixture has concluded or an exit statement is
processed. If the user specifies an iteration speed below ten seconds then we run the risk of a stack
overflow. The rule functionDef will also be executed recursively if function recursion is used. If the
user specifies a recursive depth above 300 then we again run the risk of a stack overflow.
A recursive function is similar to a loop except the former must remember the context of its calling
function. This results in a larger demand on the stack. Modern Java compilers can transform a tail-

61

Strategy Testing in Betfair Markets

recursive function into a loop using some clever optimisations; this has the effect of drastically
reducing the amount of stack space used.
The actions for both rules discussed have been written to utilize tail recursion however; the code
which ANTLR generates undoes our efforts by placing additional statements at the end of each
method.
A quick fix to this problem involves increasing the stack size offered to the JVM but this will only
prolong the inevitable. A better solution will entail extending the interpreter generated by ANTLR
and overriding the methods for strategy and functionDef. We can then adjust our implementation to
retain the desired tail recursion and greatly increase the recursive depth allowed.

7.5 EXTENDING FOR FURTHER SPORTS


A principle design decision was to develop a framework which can easily be extended to use any
sporting data offered by Fracsoft. Such additional sports include Basketball, Grey Hound Racing and
Cricket.
In order to test our strategies against a new event type, the following steps are required:

Firstly, the script which populates our database must contain a unique regular expression for
each event type we wish to add. This regular expression will define the desired break down
of the sports event hierarchy. (See Figure 5.5).
The desired data must be exported to a directory where our script has read permissions. To
ensure that our script will correctly read the XML price data, the columns highlighted in
Figure 7.1 need to be selected in the Fracsoft data manager.
Finally, execute the script in the same directory as the historical price data in order to
populate our database.

We should now be able to create and apply strategies to the new event type. No changes are
required in the structure of our database, framework or language. (Although the language can be
optionally extended to provide further sport-specific functionality)

62

Strategy Testing in Betfair Markets

Figure 7.1: Fracsoft tool to export historical price data to XML

63

Strategy Testing in Betfair Markets

7.6 EXTENDING TO EXECUTE STRATEGIES LIVE


One potential extension of the project could be to extend the testing application to execute our
evaluated strategies against live market data.
To accomplish this, the following steps could be implemented:
An additional filter Environment could be added to our event filter. We are then presented with two
possible values, Test for evaluating our strategy against historical data and Live for evaluating our
strategy against live market data.
When processing the event filter, encountering a Live environment will cause the testing suite to
instead query the Betfair database. In either case we will populate our class structure with all static
(non-price) data related to our filter criteria. The environment value will be stored locally and used
later by our strategy interpreter. When encountering a built-in method, this value will decide
whether the function logic is translated into SQL or a series of API calls.
More rigorous error handling and semantic analysis will be required to reflect the additional risks in
executing a strategy live. If we wish to use our own money to place bets then a software fault could
leave us with an unintended position in the market. For example a strategy which places a large lay
bet at the beginning of an event with the intention of hedging risk as the event progresses could lead
to us risking more than we intended.
To reduce this risk, a good intermediary step would involve using the Betfair API to read live market
data whilst only placing imaginary bets. As before we can evaluate the liability and potential profit
for a fixed-odds bet prior to its placement (Section 2.1.2). Therefore we could re-use our framework
to maintain a tally on the market. In order to evaluate our strategy, we would need to wait for the
fixture to conclude before using an API call to accurately determine the winning selections.

64

Strategy Testing in Betfair Markets

8 CONCLUSIONS
Our language provides some novel features for forming market independent betting strategies.
Numerous improvements could be made both to enhance the performance and the expressiveness
of our language, though the extent of these additions for finding winning strategies is debatable.
An attempt to infer the occurrence of in-play events is made with largely accurate results. However,
the availability of time-stamped event data would allow for far more accurate deductions. Given this
data we could develop sophisticated strategies which react accurately and timely to a sudden
change in the state of an event.
Our testing engine allows us to evaluate any strategy expressible by our language against a database
of historical market data for 100 different fixtures. This is carried out efficiently and outputs useful
statistics for evaluating a strategies performance.

65

Strategy Testing in Betfair Markets

APPENDIX A

ANTLR LEXER/PARSER GRAMMAR


grammar BetGrammar;

options {
output=AST;
ASTLabelType=MyNode;
}
tokens{
VARASSIGN;
VARDEF;
BETCOND;
VARINCR;
VARDECR;
FCALL;
FDEF;
ARG;
SLIST;
EXPR;
BET;
RS;
FTYPE;
ONGOAL;
BETOP;
}
@header {package generated; import framework.*; import language.*;}
@lexer::header{package generated; import framework.*; import language.*;}
prog: eventFilter? varStat* strategy functionDef* -> eventFilter? varStat*
strategy functionDef*
;
eventFilter
:
'EVENTS' '{' eventFilterStatement '}' -> ^('EVENTS'
eventFilterStatement)
;
eventFilterStatement
:
assignEvent (AND eventFilterExpression)* ';'
eventFilterExpression)*)
;
assignEvent
:
;

-> ^(assignEvent (AND

'EventType' '=' STRING -> ^('EventType' STRING)

eventFilterExpression

66

Strategy Testing in Betfair Markets

:
'('? filter eventFilterConditional ')'? -> ^(FTYPE filter
eventFilterConditional)
;
eventFilterConditional
:
'=' STRING -> ^('=' STRING)
|
'LIKE' STRING -> ^('LIKE' STRING)
|
'IN' '(' repeatedString ')' -> ^('IN' repeatedString)
|
'LIN' '(' repeatedString ')' -> ^('LIN' repeatedString)
;
repeatedString
:
STRING (',' STRING)* -> ^(RS STRING+)
;
filter
:
varStat
:
|
;

('Event'|'Fixture'|'Runner');

varDef
varIncrDecr

varDef
: ID ';' -> ^(VARDEF ID)
| ID '=' logicalExpression ';' -> ^(VARASSIGN ID logicalExpression)
;
//Increments / Decrements identifier
varIncrDecr
: ID ('++' ';' -> ^(VARINCR ID)
|'--' ';' -> ^(VARDECR ID)
)
;
strategy
:
'STRAT' '(' logicalExpression (',' logicalExpression)? ')' lc='{'
stat* '}' -> ^('STRAT' logicalExpression logicalExpression? ^(SLIST[$lc,"SLIST"]
stat*))
;
conditional
: 'IF' '(' logicalExpression ')' s1=condBlock
( 'ELSE' s2=condBlock -> ^('IF' ^(EXPR logicalExpression) $s1 $s2)
|
-> ^('IF' ^(EXPR logicalExpression) $s1)
)
;
onEvent
:
condBlock)
;
condBlock
:
;

'on' 'GOAL' '(' selection? ')' condBlock -> ^(ONGOAL selection?

lc = '{' stat* '}' -> ^(SLIST[$lc, "SLIST"] stat*)

stat: conditional
|
onEvent

67

Strategy Testing in Betfair Markets

|
|
|
|
;

varStat
bet
exit
print

//in-built print statements


print
:
'print' printParam ('.' printParam)* ';' -> ^('print' printParam+)
|
'println' printParam ('.' printParam)* ';' -> ^('println'
printParam+)
|
'println' ';'!
|
'print' ';'!
;
printParam
:
|
;

STRING
logicalExpression

//List of expressions with lowest precedance first


logicalExpression
:
booleanAndExpression ( OR^ booleanAndExpression )*
;
booleanAndExpression
:
equalityExpression ( AND^ equalityExpression )*
;
equalityExpression
:
relationalExpression ( (EQ^ | NEQ^) relationalExpression)*
;
relationalExpression
:
additiveExpression ( (LT^ | LTEQ^ | GT^ | GTEQ^) additiveExpression )*
;
additiveExpression
:
multiplicativeExpression ( (PLUS^ | MINUS^) multiplicativeExpression )*
;
multiplicativeExpression
:
powerExpression ( ( MULT^ | DIV^ | MOD^ ) powerExpression )*
;
powerExpression
:
unaryExpression ( POW^ powerExpression )? // right-associative via
tail recursion
;
unaryExpression
:
primaryExpression
|
NOT^ primaryExpression
| MINUS^ primaryExpression
;
primaryExpression
:
'(' logicalExpression ')' -> logicalExpression
|
value

68

Strategy Testing in Betfair Markets

;
value
:
INT
|
FLOAT
| (ID '(') => functionCall //syntactic predicate
| ID
|
betOperator
;
bet
:
betParam))
|
;

BETVAR '=' direction betParam ';' -> ^(BET ^('=' BETVAR direction
direction betParam ';' -> ^(BET direction betParam)

betParam
: '(' market ',' selection ',' stake ')' -> market selection stake
;
betOperator
:
;

BETVAR '(' betField ')' -> ^(BETOP BETVAR betField)

betField
:
|
|
|
;

'odds'
'stake'
'profit'
'liability'

market
:
|
;

marketPlaceholder
otherBetParam

marketPlaceholder
:
generalMarketPlaceholders
|
footballMarketPlaceholders
|
tennisMarketPlaceholders
|
horseracingMarketPlaceholders
|
BETVAR '('! 'market' ')'!
;
generalMarketPlaceholders
:
'MatchOdds' -> STRING["'Match Odds'"]
;
footballMarketPlaceholders
:
'HalfTime' -> STRING["'Half Time'"]
|
'OU1.5Goals' -> STRING["'Over/Under 1.5 Goals'"]
|
'OU2.5Goals' -> STRING["'Over/Under 2.5 Goals'"]
|
'OU3.5Goals' -> STRING["'Over/Under 3.5 Goals'"]
|
'OU4.5Goals' -> STRING["'Over/Under 4.5 Goals'"]
|
'CorrectScore' -> STRING["'Correct Score'"]
|
'NextGoal' -> STRING["'Next Goal'"]
|
'HalfTime/FullTime' -> STRING["'Half Time/Full time'"]
|
'HalfTimeScore' -> STRING["'Half Time Score'"]
;

69

Strategy Testing in Betfair Markets

tennisMarketPlaceholders
:
'SetBetting' ->
;

STRING["'Set Betting'"]

horseracingMarketPlaceholders
:
'ToBePlaced' -> STRING["'To Be Placed'"]
;
selection
:
|
;

selectionPlaceholder
otherBetParam

selectionPlaceholder
:
generalSelectionPlaceholders
|
footballSelectionPlaceholders
|
BETVAR '('! 'selection' ')'!
;
generalSelectionPlaceholders
:
'Favourite' '('! market ','! INT ')'! //favourite selection
|
'Runner' '('! logicalExpression ')'!
;
footballSelectionPlaceholders
@init{String v = "";}
:
('Home'|'Away')
|
('Home'|'Away'|'Draw') '/' ('Home'|'Away'|'Draw')
|
('NilNil'{v="'0 - 0'";} |'NilOne'{v="'0 - 1'";}
|
'NilTwo'{v="'0 - 2'";}|'NilThree'{v="'0 - 3'";}
|
'OneNil'{v="'1 - 0'";}|'OneOne'{v="'1 - 1'";}
|
'OneTwo'{v="'1 - 2'";}|'OneThree'{v="'1 - 3'";}) -> STRING[v]
|
('TwoNil'{v="'2 - 0'";}|'TwoOne'{v="'2 - 1'";}
|
'TwoTwo'{v="'2 - 2'";}|'TwoThree'{v="'2 - 3'";}
|
'ThreeNil'{v="'3 - 0'";}|'ThreeOne'{v="'3 - 1'";}
|
'ThreeTwo'{v="'3 - 2'";}|'ThreeThree'{v="'3 - 3'";}) -> STRING[v]
|
'CurrentScore'
|
'Under1.5Goals' -> STRING["'Under 1.5 Goals'"]
|
'Under2.5Goals' -> STRING["'Under 2.5 Goals'"]
|
'Under3.5Goals' -> STRING["'Under 3.5 Goals'"]
|
'Under4.5Goals' -> STRING["'Under 4.5 Goals'"]
|
'Over1.5Goals' -> STRING["'Over 1.5 Goals'"]
|
'Over2.5Goals' -> STRING["'Over 2.5 Goals'"]
|
'Over3.5Goals' -> STRING["'Over 3.5 Goals'"]
|
'Over4.5Goals' -> STRING["'Over 4.5 Goals'"]
|
'Draw' -> STRING["'The Draw'"]
|
'NoGoal' -> STRING["'No Goal'"]
|
'AnyUnqouted' -> STRING["'Any Unquoted'"]
;
stake
:
;

logicalExpression

otherBetParam
:
STRING
;

70

Strategy Testing in Betfair Markets

//Terminates the betting strategy


exit: 'exit' ';'!
;
functionCall
:
ID '(' paramList? ')' -> ^(FCALL ID paramList?)
;
paramList
:
;

parameter (',' parameter)*

parameter
:
|
|
|
|
;

logicalExpression
selectionPlaceholder
marketPlaceholder
STRING
BETVAR -> STRING[$BETVAR.text]

-> parameter+

functionDef
: 'function' ID '(' formalParamList? ')' functionBlock -> ^(FDEF ID
formalParamList? functionBlock)
;
functionBlock
:
lc = '{' stat* returnVar '}' -> ^(SLIST[$lc, "SLIST"] stat*
returnVar) // SLIST derived from left { so that line and column info is sopied to
imaginary node for err/debugging
;
returnVar
:
;

'return' logicalExpression ';' -> ^('return' logicalExpression)

formalParamList
:
ID (',' ID)* -> ^(ARG ID+)
;
direction
:
;

OR
AND
EQ
NEQ
LT
LTEQ
GT
GTEQ
PLUS
MINUS
MULT
DIV
MOD
POW
NOT

:
:

'BACK' | 'LAY'

'||' | 'or' || 'OR';


'&&' | 'and' || 'AND';
:
'=' | '==';
:
'!=' | '<>';
:
'<';
:
'<=';
:
'>';
:
'>=';
:
'+';
:
'-';
:
'*';
:
'/';
:
'%';
:
'^';
:
'!' | 'not';

71

Strategy Testing in Betfair Markets

BOOLEAN :

'true'|'false';

fragment LETTER : ('a'..'z' | 'A'..'Z');


fragment DIGIT : ('0'..'9');
INT
: DIGIT+ ;
FLOAT : DIGIT '.' DIGIT+;
STRING :
'\'' (~ '\'' )* '\'';
ID : LETTER (LETTER|DIGIT|'_')*;
BETVAR :
'$' (LETTER|DIGIT|'_')*;
NEWLINE
:
('\r'? '\n')+ {skip();}; //Different new line definitions for
diff platforms
WS
:
(' '|'\t'| '\u000C')+ { $channel = HIDDEN; }; //Sends a series of
space and tab characters to hidden channel
COMMENT :
'//' ~('\r' | '\n')* NEWLINE { skip(); }; //Single line comments
MULTI_COMMENT options {greedy = false;} //multi-line comment
:
'/*' .* '*/' NEWLINE? {skip();};

72

Strategy Testing in Betfair Markets

ANTLR SEMANTIC TREE GRAMMAR: PART 1


tree grammar SymbolTreeGrammar;
options {
tokenVocab=BetGrammar; //re-use token types
ASTLabelType=MyNode;
output=AST;
rewrite=true;
}
@header {
package generated;
import java.util.ArrayList;
import framework.*;
import language.*;
}
@members{
Scope globalScope = new Scope(null); //null as top level scope - i.e. no parents
Scope currentScope = globalScope;
static ArrayList<String> methodList = Resource.getMethodList();
public void pushScope(){
currentScope = new Scope(currentScope);
}
public void popScope(){
currentScope = currentScope.pop();
}
public static boolean isBuiltInMethod(String methodName){
boolean builtInMethod = false;
for(int i = 0; i < methodList.size(); i++){
if(methodList.get(i).equals(methodName)){
builtInMethod = true;
}
}
return builtInMethod;
}
public String removeQuotes(String s){
String newString = s.replace("'", "");
return newString;
}
public boolean checkLegalSelection(String selection){
return Resource.checkLegalSelection(selection);
}
public boolean checkLegalMarket(String market){
return Resource.checkLegalMarket(market);
}
private static void addToErrorList(String error){
Resource.addToErrorList(error);

73

Strategy Testing in Betfair Markets

}
private static void print(Object obj) {
System.out.print(obj);
}
private static void println(Object obj) {
System.out.println(obj);
}
}
prog
@init{Scope.setTop(globalScope);}
:
eventFilter? varStat* strategy functionDef*
;
eventFilter
:

^('EVENTS' eventFilterStatement)
{DbWrapper.populateFixtures();}

;
eventFilterStatement
:
^('EventType' STRING
{DbWrapper.addEventType(removeQuotes($STRING.text));}
(AND eventFilterExpression)*)
;
eventFilterExpression
:
^(FTYPE f=filter eventFilterConditional[$f.text])
;
eventFilterConditional [String f]
:
^('=' STRING) {DbWrapper.addFilter(f, "equals",
removeQuotes($STRING.text));}
|
^('LIKE' STRING) {DbWrapper.addFilter(f, "like",
removeQuotes($STRING.text));}
|
^('IN' repeatedString[f, "in"])
|
^('LIN' repeatedString[f, "lin"])
;
repeatedString [String f, String ft]
:
^(RS (STRING {DbWrapper.addFilter(f, ft,
removeQuotes($STRING.text));})+)
;
filter
:
;
varStat
:
|
;

'Event' | 'Fixture' | 'Runner'

varDef
varIncrDecr

varDef
: ^(VARDEF ID)
{
VariableSymbol s = (VariableSymbol)currentScope.checkScope($ID.text);
if(s == null){ //only allow a var definition if it has not been already
defined in the current scope

74

Strategy Testing in Betfair Markets

s = (VariableSymbol)currentScope.defineVariable($ID.getText());
s.definition = $VARDEF;
$ID.symbol = s;
}
else{
addToErrorList("ERROR: variable "+$ID.text+" has already been
defined in this scope");
}
}
| ^(VARASSIGN ID logicalExpression)
{
VariableSymbol s = (VariableSymbol)currentScope.lookup($ID.text);
if(s!=null) {
$ID.symbol = s;
}
else if (s==null){ //Variable does not currently exist so create in current
scope
s = (VariableSymbol)currentScope.defineVariable($ID.getText());
s.definition = $VARASSIGN;
$ID.symbol = s;
}
else{
addToErrorList("ERROR: cannot assign to an undefined variable
"+$ID.text);
}
}
;
varIncrDecr
: ^(VARINCR ID)
{
VariableSymbol s = (VariableSymbol)currentScope.lookup($ID.text);
if(s != null){
$ID.symbol = s;
}
else{
addToErrorList("ERROR: cannot increment undefined variable
"+$ID.text);
}
}
| ^(VARDECR ID)
{
VariableSymbol s = (VariableSymbol)currentScope.lookup($ID.text);
if(s != null){
$ID.symbol = s;
}
else{
addToErrorList("ERROR: cannot decrement undefined variable
"+$ID.text);
}
}
;

strategy
@init{pushScope();}
@after{popScope();}
:
^('STRAT' logicalExpression logicalExpression? ^(SLIST stat*))
;

75

Strategy Testing in Betfair Markets

conditional
: ^('IF' ^(EXPR logicalExpression) condBlock condBlock?)
;
onEvent
:
;

^(ONGOAL selection? condBlock)

condBlock
@init{pushScope();}
@after{popScope();}
:
^(SLIST stat*)
;
stat: conditional
|
onEvent
|
varStat
|
bet
|
exit
|
print
;
print
:
|
|
|
;
printParam
:
|
;

^('print' printParam+)
^('println' printParam+)
'println'
'print'

STRING
logicalExpression

//List of expressions with lowest precedance first


logicalExpression
:
^(OR logicalExpression logicalExpression)
| ^(AND logicalExpression logicalExpression)
| ^(EQ logicalExpression logicalExpression)
| ^(NEQ logicalExpression logicalExpression)
| ^(LT logicalExpression logicalExpression)
| ^(LTEQ logicalExpression logicalExpression)
| ^(GT logicalExpression logicalExpression)
| ^(GTEQ logicalExpression logicalExpression)
| ^(PLUS logicalExpression logicalExpression)
| (^(MINUS logicalExpression logicalExpression)) => ^(MINUS logicalExpression
logicalExpression)
| ^(MULT logicalExpression logicalExpression)
| ^(DIV logicalExpression logicalExpression)
| ^(MOD logicalExpression logicalExpression)
| ^(POW logicalExpression logicalExpression)
| ^(NOT logicalExpression)
| ^(MINUS logicalExpression)
| INT
|
FLOAT
| ID {
VariableSymbol s = (VariableSymbol)currentScope.lookup($ID.text);
if(s!=null){

76

Strategy Testing in Betfair Markets

$ID.symbol = s; //Store reference of symbol


}
else{
addToErrorList("ERROR: undefined variable "+$ID.text);
}
}
| functionCall //syntactic predicates can be removed
| betOperator
;

betOperator
:
;

^(BETOP BETVAR betField)

betField
:
|
|
|
;

'odds'
'stake'
'profit'
'liability'

bet
:
|
;
betParam
:

^(BET ^('=' BETVAR direction betParam))


^(BET direction betParam)

m=market s=selection stake


{
//if market/selection are not null then check against the list of

legal names
if(DbWrapper.isConnected()){
if($m.value != null){
boolean isLegalMarket = checkLegalMarket($m.value);
if(!isLegalMarket){
addToErrorList("ERROR: Argument: '"+$m.value+"' is not
a legal market");
}
}
if($s.value != null){
boolean isLegalSelection =
checkLegalSelection($s.value);
if(!isLegalSelection){
addToErrorList("ERROR: Argument: '"+$s.value+"' is not
a legal selection");
}
}
}
}
;
market returns [String value]
:
mp=marketPlaceholder {$value = $mp.value;}
|
bp=otherBetParam {$value = $bp.value;}
;
marketPlaceholder returns [String value]

77

Strategy Testing in Betfair Markets

BETVAR 'market' {$value = null;} //only realised at interpreter

stage
;
selection returns [String value]
:
sp=selectionPlaceholder {$value = $sp.value;}
|
bp=otherBetParam {$value = $bp.value;}
;
selectionPlaceholder returns [String value]
:
gsp=generalSelectionPlaceholders {$value = $gsp.value;}
|
fsp=footballSelectionPlaceholders {$value = $fsp.value;}
|
BETVAR 'selection' {$value = null;} //only realised at interpreter
stage
;
generalSelectionPlaceholders returns [String value]
:
'Favourite' market INT {$value = null;}
|
'Runner' logicalExpression {$value = null;}
;
footballSelectionPlaceholders returns [String value]
:
('Home'|'Away') {$value = null;} //only realised at interpreter
stage
|
('Home'|'Away'|'Draw') '/' ('Home'|'Away'|'Draw') {$value = null;}
//only realised at interpreter stage
|
'CurrentScore' {$value = null;} //only realised at interpreter stage
;
stake
:
;

logicalExpression

otherBetParam returns [String value]


:
STRING {$value = removeQuotes($STRING.text);}
;
//Terminates the betting strategy
exit: 'exit'
;
functionCall
@init{FunctionSymbol s = null;}
:
^(FCALL ID // ^(FCALL ID paramList?)
{
s = (FunctionSymbol)currentScope.lookup($ID.text);
if(s != null){ //If function has already been defined
$FCALL.symbol = s; //Store reference of symbol
}
else{ //create dummy table entry
s = (FunctionSymbol)globalScope.defineDummyFunction($ID.text);
$FCALL.symbol = s;
if(isBuiltInMethod($ID.text)){ //Function name is the same as a
built in function
s.builtInFunction = true;
}
}
}
paramList?)

78

Strategy Testing in Betfair Markets

;
paramList
:
;

parameter+

parameter
:
|
|
|
;

logicalExpression
marketPlaceholder
selectionPlaceholder
STRING

functionDef
@init{FunctionSymbol s = null; int startStreamIndex=input.index(); pushScope();}
@after{popScope();}
:
^(FDEF ID
{
$FDEF.streamIndex = startStreamIndex; //Add start stream to tree
node
s = (FunctionSymbol)globalScope.lookup($ID.text);
if(s != null && s.dummy == true){//Dummy symbol has been defined
s.convertDummy(globalScope);//convert dummy to normal function
definition
s.definition = $FDEF;
$FDEF.symbol = s;
if(isBuiltInMethod($ID.text)){ //Function name is the same as
a built in function
s.builtInFunction = true;
}
}
else if(s != null){//function has already been defined
addToErrorList("ERROR: function "+$ID.text+" has already been
defined");
}
else{//define function - will occur if function definition comes
before function call
s = (FunctionSymbol)globalScope.defineFunction($ID.text);
s.definition = $FDEF;
$FDEF.symbol = s;
if(isBuiltInMethod($ID.text)){ //Function name is the same as
a built in function
s.builtInFunction = true;
}
}
}
formalParamList[s]? functionBlock)
;
formalParamList[FunctionSymbol f]
:
^(ARG (ID //^(ARG ID+)
{
VariableSymbol s = (VariableSymbol)currentScope.checkScope($ID.text);
if(s == null){ //only allow a var definition if it has not been already
defined in the current scope - prevents multiple arguments with same name
s = (VariableSymbol)currentScope.defineVariable($ID.getText());

79

Strategy Testing in Betfair Markets

s.definition = $ARG;
$ID.symbol = s;
f.numArgs++;
}
else{
addToErrorList("ERROR: variable "+$ID.text+" has already been
defined in this scope");
}
})+)
;
functionBlock
:
^(SLIST stat* returnVar)
;
returnVar
:
;

^('return' logicalExpression)

direction
:
;

'BACK' | 'LAY'

80

Strategy Testing in Betfair Markets

ANTLR SEMANTIC TREE GRAMMAR: PART 2


tree grammar FuncTreeGrammar;
options {
tokenVocab=BetGrammar; //re-use token types
ASTLabelType=MyNode;
output=AST;
rewrite=true;
}
@header {
package generated;
import java.util.ArrayList;
import framework.*;
import language.*;
}
@members{
private static void addToErrorList(String error){
Resource.addToErrorList(error);
}
private static void print(Object obj) {
System.out.print(obj);
}
private static void println(Object obj) {
System.out.println(obj);
}
}
prog: eventFilter? varStat* strategy functionDef*
;
eventFilter
:
;

^('EVENTS' eventFilterStatement)

eventFilterStatement
:
^('EventType' STRING (AND eventFilterExpression)*)
;
eventFilterExpression
:
^(FTYPE filterType eventFilterConditional)
;
eventFilterConditional
:
^('=' STRING)
|
^('IN' repeatedString)
|
^('LIKE' STRING)
|
^('LIN' repeatedString)
;
repeatedString

81

Strategy Testing in Betfair Markets

:
;
filterType
:

varStat
:
|
;

^(RS STRING+)

('Event'|'Fixture'|'Runner');

varDef
varIncrDecr

varDef
: ^(VARDEF ID)
| ^(VARASSIGN ID logicalExpression)
;
varIncrDecr
: ^(VARINCR ID)
| ^(VARDECR ID)
;
strategy
:
^('STRAT' logicalExpression logicalExpression? ^(SLIST stat*))
//Wish to start a new scope when entering Strat block
;
conditional
: ^('IF' ^(EXPR logicalExpression) condBlock condBlock?)
;
onEvent
:
;

^(ONGOAL selection? condBlock)

condBlock
:
;

^(SLIST stat*)

stat: conditional
|
onEvent
|
varStat
|
bet
|
exit
|
print
;
print
:
^('print' printParam+)
|
^('println' printParam+)
|
'println'
|
'print'
;
printParam
:
|
;

STRING
logicalExpression

//List of expressions with lowest precedance first

82

Strategy Testing in Betfair Markets

logicalExpression
:
^(OR logicalExpression logicalExpression)
| ^(AND logicalExpression logicalExpression)
| ^(EQ logicalExpression logicalExpression)
| ^(NEQ logicalExpression logicalExpression)
| ^(LT logicalExpression logicalExpression)
| ^(LTEQ logicalExpression logicalExpression)
| ^(GT logicalExpression logicalExpression)
| ^(GTEQ logicalExpression logicalExpression)
| ^(PLUS logicalExpression logicalExpression)
| (^(MINUS logicalExpression logicalExpression)) => ^(MINUS logicalExpression
logicalExpression)
| ^(MULT logicalExpression logicalExpression)
| ^(DIV logicalExpression logicalExpression)
| ^(MOD logicalExpression logicalExpression)
| ^(POW logicalExpression logicalExpression)
| ^(NOT logicalExpression)
| ^(MINUS logicalExpression)
| INT
|
FLOAT
| ID
| functionCall //syntactic predicates can be removed
| betOperator
;
betOperator
:
;

^(BETOP BETVAR betField)

betField
:
|
|
|
;

'odds'
'stake'
'profit'
'liability'

bet
:
|
;
betParam
:
;

^(BET ^('=' BETVAR direction betParam))


^(BET direction betParam)

market selection stake

market
:
|
;

marketPlaceholder
otherBetParam

marketPlaceholder
:
BETVAR 'market'
;
selection
:
|
;

selectionPlaceholder
otherBetParam

83

Strategy Testing in Betfair Markets

selectionPlaceholder
:
generalSelectionPlaceholders
|
footballSelectionPlaceholders
|
BETVAR 'selection'
;
generalSelectionPlaceholders
:
'Favourite' market INT
|
'Runner' logicalExpression
;
footballSelectionPlaceholders
:
('Home'|'Away')
|
('Home'|'Away'|'Draw') '/' ('Home'|'Away'|'Draw')
|
'CurrentScore'
;
stake
:
;

logicalExpression

otherBetParam
:
STRING
;
//Terminates the betting strategy
exit: 'exit'
;
functionCall
@init{FunctionSymbol s = null;}
:
^(FCALL ID p=paramList?)
{
s = (FunctionSymbol)$FCALL.symbol;
if(s != null && !s.dummy && !s.builtInFunction){ //if legitimate
function call
int numParam = $p.numParam;
int numArg = s.getNumArguments();
if(numParam != numArg){
addToErrorList("ERROR: function "+$ID.text+" was called
with "+numParam+" parameters however expects "+numArg+" arguments");
}
}
else if(s != null && s.dummy && !s.builtInFunction){
addToErrorList("ERROR: function "+$ID.text+" does not
reference a user-defined OR built-in function definition");
}
else if(s != null && !s.dummy && s.builtInFunction){
addToErrorList("ERROR: user-defined function "+$ID.text+" has
the same name as a built-in function");
}
}
;
paramList returns [int numParam]
@init {int n = 0;}
@after{$numParam = n;}

84

Strategy Testing in Betfair Markets

:
;

(p=parameter{n++;})+

parameter
:
|
|
;

logicalExpression
selectionPlaceholder
STRING

functionDef
:
;

^(FDEF ID formalParamList? functionBlock)

formalParamList
:
^(ARG ID+)
;
functionBlock
:
^(SLIST stat* returnVar)
;
returnVar
:
;
direction
:

^('return' logicalExpression)

'BACK' | 'LAY';

85

Strategy Testing in Betfair Markets

ANTLR STRATEGY INTERPRETER


tree grammar BetTreeGrammar;
options {
tokenVocab=BetGrammar; //re-use token types
ASTLabelType=MyNode;
}
@header {
package generated;
import framework.*;
import language.*;
}
@members{
Fixture fixture = Main.currentFixture;
FunctionLibrary funLib = new FunctionLibrary();
Scope globalScope = new Scope(null);
Scope currentScope = globalScope;
int endOfStream = input.size();
int stratBlockIndex;
int stratBlockCounter = 0;
public void pushScope(){
currentScope = new Scope (currentScope);
}
public void popScope(){
currentScope = currentScope.pop();
}
public String removeQuotes(String s){
String newString = "";
if(s != null){
newString = s.replace("'", "");
}
return newString;
}
//checks paramList for any null values
public boolean checkParamList(Object[] paramList){
boolean isLegal = true;
for(int i = 0; i < paramList.length; i++){
if(paramList[i] == null){
isLegal = false;
}
}
return isLegal;
}
//Shortens code for print and println

86

Strategy Testing in Betfair Markets

private static void print(Object obj) {


System.out.print(obj);
}
private static void println(Object obj) {
System.out.println(obj);
}
}
prog
:

eventFilter? varStat* strategy


{
input.seek(endOfStream);
println("End of strategy");
}

;
eventFilter
:
;

^('EVENTS' eventFilterStatement)

eventFilterStatement
:
^('EventType' STRING (AND eventFilterExpression)*)
;
eventFilterExpression
:
^(FTYPE filterType eventFilterConditional)
;
eventFilterConditional
:
^('=' STRING)
|
^('IN' repeatedString)
|
^('LIKE' STRING)
|
^('LIN' repeatedString)
;
repeatedString
:
^(RS STRING+)
;
filterType
:
varStat
:
|
;

('Event'|'Fixture'|'Runner');

varDef
varIncrDecr

varDef
: ^(VARDEF ID)
{
VariableSymbol s = (VariableSymbol)currentScope.checkScope($ID.text);
if(s == null){ //only allow a var definition if it has not been
already defined in the current scope
s = (VariableSymbol)currentScope.defineVariable($ID.getText());
}
}
| ^(VARASSIGN ID e=logicalExpression)
{

87

Strategy Testing in Betfair Markets

VariableSymbol s =
(VariableSymbol)currentScope.lookup($ID.getText());
if(s != null){
s.addValue($e.value);
}
else{ //Variable does not currently exist so create in local scope
s = (VariableSymbol)currentScope.defineVariable($ID.getText());
s.addValue($e.value);
}
}
;
varIncrDecr
: ^(VARINCR ID)
{
if($ID.symbol != null){
VariableSymbol s =
(VariableSymbol)currentScope.lookup($ID.getText());
double newValue = s.getValue()+1;
s.addValue(newValue);
}
}
| ^(VARDECR ID)
{
if($ID.symbol != null){
VariableSymbol s =
(VariableSymbol)currentScope.lookup($ID.getText());
double newValue = s.getValue()-1;
s.addValue(newValue);
}
}
;
strategy
@init {int statIdx = 0; stratBlockIndex = input.index(); pushScope();
stratBlockCounter++;}
:
^('STRAT' l1=logicalExpression
(l2=logicalExpression
{
if(stratBlockCounter == 1){ //only executed on first entry into
strategy block
fixture.setStrategyTime($l2.value);
}
})?
^(SLIST ( {statIdx = input.index();} s=. {if($s != null
&& !fixture.isEventFinished){input.seek(statIdx);stat();}} )*)) //only evaluate a
statement if the event is not finished
{
popScope();
double seconds = $l1.value;
fixture.incrementTime(seconds);
if(!fixture.isEventFinished){
input.seek(stratBlockIndex);
strategy(); //re-iterate over strategy
}
}
;
conditional

88

Strategy Testing in Betfair Markets

@init {int ifStatIdx=0; int elseStatIdx=0;}


:
^('IF' ^(EXPR c=logicalExpression) // ^('IF' logicalExpression condBlock
condBlock?)
{ifStatIdx = input.index();} s=.
{elseStatIdx = input.index();} e=.?)
{
int next = input.index();
if ( $c.value == 1 ) { //Condition evaluates to true
input.seek(ifStatIdx);
condBlock();
}
else if ( $e!=null ) {
input.seek(elseStatIdx);
condBlock();
}
input.seek(next);
}
;
onEvent
@init {int cBlock = 0;}
:
^(ONGOAL s=selection?

{cBlock = input.index();} c=.)

{
int next = input.index();
double goal = 0;
if(s!=null){//user provided argument
String selectionName = removeQuotes($s.value);
goal = fixture.goalScored(selectionName);
if(goal == -1){ // if no results were found
System.out.print("Warning: the selection
'"+selectionName+"' is not valid for this fixture. ");
System.out.println("Skipping current fixture..");
fixture.concludeEvent();
}
}
else{//else check if a goal was scored (we dont care by whom)
goal = fixture.goalScored();
}
if ( goal == 1.0 ) { //goal condition evaluates to true
input.seek(cBlock);
condBlock();
}
input.seek(next);
}
;
condBlock
@init{pushScope(); int statId = 0;}
@after{popScope();}
:
^(SLIST ({statId = input.index();} st=. {if($st != null
&& !fixture.isEventFinished){input.seek(statId);stat();}})*) // ^(SLIST (stat)*)
;
stat: conditional

89

Strategy Testing in Betfair Markets

|
|
|
|
|
;

onEvent
varStat
bet
exit
print

print
@init{String params = "";}
:
^('print' (p=printParam {params += $p.value;} )+)
{print(params);}
|
^('println' (p=printParam {params += $p.value;} )+)
{println(params);}
|
'println' {println(params);}
|
'print'
;
printParam returns [String value]
:
STRING {$value = removeQuotes($STRING.text);}
|
l=logicalExpression {$value = Double.toString($l.value);}
;
//List of expressions with lowest precedance first
logicalExpression returns [double value]
:
^(OR a=logicalExpression b=logicalExpression) {$value = ($a.value == 1 ||
$b.value == 1) ? 1:0;}
| ^(AND a=logicalExpression b=logicalExpression) {$value = ($a.value == 1 &&
$b.value == 1) ? 1:0;}
| ^(EQ a=logicalExpression b=logicalExpression) {$value = $a.value ==
$b.value? 1:0;}
| ^(NEQ a=logicalExpression b=logicalExpression) {$value = $a.value !=
$b.value? 1:0;}
| ^(LT a=logicalExpression b=logicalExpression) {$value = $a.value <
$b.value?1:0;}
| ^(LTEQ a=logicalExpression b=logicalExpression) {$value = $a.value <=
$b.value?1:0;}
| ^(GT a=logicalExpression b=logicalExpression) {$value = $a.value >
$b.value ? 1:0;}
| ^(GTEQ a=logicalExpression b=logicalExpression) {$value = $a.value >=
$b.value ? 1:0;}
| ^(PLUS a=logicalExpression b=logicalExpression) {$value = $a.value +
$b.value; }
| (^(MINUS a=logicalExpression b=logicalExpression))=>^(MINUS
a=logicalExpression b=logicalExpression){ $value = $a.value - $b.value; }
//requires a syntactic predicate
| ^(MULT a=logicalExpression b=logicalExpression) { $value = $a.value *
$b.value; }
| ^(DIV a=logicalExpression b=logicalExpression) { $value = $a.value /
$b.value; }
| ^(MOD a=logicalExpression b=logicalExpression) //{ $value = $a.value %
$b.value; }
| ^(POW a=logicalExpression b=logicalExpression)
| ^(NOT a=logicalExpression) {$value = ($a.value == 0) ? 1 : 0;}
| ^(MINUS a=logicalExpression) { $value = -$a.value; }
| INT
{$value = Double.parseDouble($INT.text);}
|
FLOAT {$value = Double.parseDouble($FLOAT.text);}
| ID {
VariableSymbol s =
(VariableSymbol)currentScope.lookup($ID.getText());

90

Strategy Testing in Betfair Markets

if(s != null){
$value = s.getValue();
}
}
| f=functionCall {$value = $f.value;}
| bo=betOperator {$value = $bo.value;}
;

betOperator returns [double value]


:
^(BETOP BETVAR b=betField)
{
Bet bet = fixture.bets.get($BETVAR.text);
if(bet != null){
if(($b.text).equals("odds")){
$value = bet.odds;
}
else if(($b.text).equals("stake")){
$value = bet.stake;
}
else if(($b.text).equals("profit")){
$value = bet.potentialProfit;
}
else if(($b.text).equals("liability")){
$value = -bet.liability;
}
}
else{
$value = 0;
}
}
;
betField
:
|
|
|
;

'odds'
'stake'
'profit'
'liability'

bet
:
|
;

^(BET ^('=' BETVAR d=direction betParam[$d.text, $BETVAR.text]))


^(BET d=direction betParam[$d.text, "null"])

betParam [String direction, String label]


:
m=market sl=selection s=stake
{
String marketName = removeQuotes($m.value);
String selectionName = removeQuotes($sl.value);
double stake = $s.value;
Market market = fixture.getMarket(marketName);
Selection selection = null;
if(market != null){
selection = market.getSelection(selectionName);
}

91

Strategy Testing in Betfair Markets

if(market != null && selection != null){


Bet b = new Bet(label, direction, market, selection, stake);
boolean betPlaced = b.placeBet(fixture.currTimeMs);
if(betPlaced){
fixture.addBet(label, b);
println("Placing bet: "+direction+"("+marketName+",
"+selectionName+", "+stake+") at Odds: "+b.odds+", TimeStamp:
"+b.timeStampPlaced+", Minute: "+(int)funLib.minute()+", WinningBet: "+b.betWon);
}
else{
println("Market suspended, bet unsuccessful");
}
}
else{
System.out.print("Warning: either the market or selection in
'"+direction+"("+marketName+", "+selectionName+", "+stake+"' is not valid for this
fixture. ");
System.out.println("Skipping current fixture..");
fixture.concludeEvent();
}
}
;
market returns [String value]
:
mp=marketPlaceholder {$value = $mp.value;}
|
bp=otherBetParam {$value = $bp.value;}
;
marketPlaceholder returns [String value]
:
BETVAR 'market'
{
Bet b = fixture.bets.get($BETVAR.text);
if(b != null){
$value = b.market.marketName;
}
}
;
selection returns [String value]
:
sp=selectionPlaceholder {$value = $sp.value;}
|
BETVAR 'selection'
{
Bet b = fixture.bets.get($BETVAR.text);
if(b != null){
$value = b.selection.selectionName;
}
}
|
bp=otherBetParam {$value = $bp.value;}
;

selectionPlaceholder returns [String value]


:
gsp=generalSelectionPlaceholders {$value = $gsp.value;}
|
fsp=footballSelectionPlaceholders {$value = $fsp.value;}
;
generalSelectionPlaceholders returns [String value]
:
'Favourite' m=market INT

92

Strategy Testing in Betfair Markets

{
String marketName = removeQuotes($m.value);
int positionByOdds = Integer.parseInt($INT.text);
$value = fixture.getFavouriteSelection(marketName, positionByOdds);
}
'Runner' l=logicalExpression
{
int runnerNum = (int) $l.value ;
$value = fixture.getRunnerName(runnerNum);
}

;
footballSelectionPlaceholders returns [String value]
:
r=('Home'|'Away')
{
int output = ($r.text.equals("Home")) ? 0 : 1;
$value = fixture.getRunnerName(output);
}
|
r1=('Home'|'Away'|'Draw') '/' r2=('Home'|'Away'|'Draw')
{
int output1 = ($r1.text.equals("Home")) ? 0 :
($r1.text.equals("Away"))?1:-1;
int output2 = ($r2.text.equals("Home")) ? 0 :
($r2.text.equals("Away"))?1:-1;
$value =
fixture.getRunnerName(output1)+"/"+fixture.getRunnerName(output2);
}
|
'CurrentScore'
{
$value = fixture.getCurrentScore();
}
;
stake returns [double value]
:
e=logicalExpression {$value = $e.value;}
;
otherBetParam returns [String value]
:
STRING {$value = $STRING.text;}
;
//Terminates the betting strategy
exit: 'exit'
{fixture.concludeEvent();}
;
functionCall returns [double value]
@init{FunctionSymbol s = null;}
:
^(FCALL ID // ^(FCALL ID paramList?)
{
s = (FunctionSymbol)$FCALL.symbol;
s.incrFunctionDepth();
}
paramList[s]?)
{
int next = input.index();
if(s.builtInFunction){//call built-in Method
Class paramTypes[] = s.getParamTypes();

93

Strategy Testing in Betfair Markets

Object paramList[] = s.getParamList();


Object retObject = funLib.executeMethod($ID.text, paramTypes,
paramList);
Double retVal = (Double)retObject;
double val = retVal.doubleValue();
if(val == -2.0){ //return value if either an invalid market or
selection argument was passed in
System.out.print("Warning: 1 or more parameters used
for function: '"+$ID.text+"' may not be valid for the current fixture. ");
System.out.println("Skipping current fixture..");
fixture.concludeEvent();
}
else{
s.addReturnValue(val);
}
}
else{
int functionDefNode = s.definition.streamIndex;
input.seek(functionDefNode);
functionDef();
}
$value = s.getReturnValue();
input.seek(next);
s.decrFunctionDepth();
}
;
paramList [FunctionSymbol s]
:
(parameter[s])+
;
parameter [FunctionSymbol s]
:
e=logicalExpression
{s.addParameter($e.value);}
|
sp=selectionPlaceholder
{
String newString = $sp.value;
s.addStringParameter(newString);
}
|
STRING
{
String newString = removeQuotes($STRING.text);
s.addStringParameter(newString);
}
;
functionDef
@init{FunctionSymbol s = null; pushScope();}
@after{popScope();}
:
^(FDEF ID
{s = (FunctionSymbol)$FDEF.symbol;}
formalParamList[s]?
{s.updateArgValues();} //Assign values to symbols
functionBlock[(FunctionSymbol)$FDEF.symbol])
;

94

Strategy Testing in Betfair Markets

formalParamList[FunctionSymbol f]
:
^(ARG (ID
{
VariableSymbol s =
(VariableSymbol)currentScope.checkScope($ID.text);
if(s == null){
s = (VariableSymbol)currentScope.defineVariable($ID.getText());
f.addArgument(s); //Add arguments to function
}
}
)+)
;
functionBlock [FunctionSymbol s]
@init{int statId = 0;}
:
^(SLIST ({statId = input.index();} st=. {if($st != null
&& !fixture.isEventFinished){input.seek(statId);stat();}})* returnVar[s]) //
^(SLIST (stat)* returnVar[s])
;
returnVar [FunctionSymbol s]
:
^('return' e=logicalExpression)
{s.addReturnValue($e.value);}
;
direction
:
;

'BACK' | 'LAY'

95

Strategy Testing in Betfair Markets

9 BIBLIOGRAPHY
1. Betfair. (n.d.) Betfair. [Online]. Available from: http://www.betfair.com/.
2. Fracsoft Ltd. (2007) Fracsoft Electronic Market Trade and Analysis. [Online].
Available from: http://www.fracsoft.com/.
3. Wikimedia Foundation. (n.d.) Wikipedia. [Online].
Available from: http://en.wikipedia.org/wiki/Betfair.
4. Andreff, W. (ed.) & Szymanski, S. (ed.) (2007) Handbook on the Economics of Sport.
Cheltenham, Edward Elgar Publishing Ltd.
5. Betfair. (2008) Betfair - Pricing Model. [Online]. Available from: http://contentcache.betfair.com/aboutus/content.asp?sWhichKey=Betfair%20Charges.
6. Cobb, J. (10 September 2008) Betfair faces criticism for massive rise in charges. The Independent.
[Online] Available from: http://www.independent.co.uk/sport/racing/betfair-faces-criticism-formassive-rise-in-charges-924359.html.
7. Betfair. (n.d.) Betfair Developers Program. [Online].
Available from: http://bdp.betfair.com/index.php.
8. Fracsoft. (2007) Fracsoft Historical Data Pricing. [Online]. Available from:
http://www.fracsoft.com/index.php?option=com_content&task=view&id=80&Itemid=111.
9. Betfair. (2009) Betfair Solutions Directory - Automated Trading. [Online].
Available from: http://solutions.betfair.com/index.php/Sports/Automated-Trading/.
10. Bet Angel. (2007) The Ultimate Betfair & BETDAQ Toolkit. [Online].
Available from: http://www.betangel.com/.
11. Parr, T. (2007) The Definitive ANTLR Reference: Building Domain-Specific Languages .
Raleigh, Pragmatic Bookshelf.
12. ANTLR, ANother Tool for Language Recognition. (2006) ANTLR 3.0 Lookahead Analysis. [Online].
Available from: http://www.antlr.org/blog/antlr3/lookahead.tml.
13. Barclays Premier League. (n.d.) Barclays Premier League Fixtures . [Online]
Available from: http://www.premierleague.com/page/FixturesResults/0,,12306,00.html.

Vous aimerez peut-être aussi