Home/Blog/Building Your First Betfair Bot
Sub Article · Software Deep Dives

Building Your First Betfair Bot in Python (2026)

Build a working Betfair trading bot in Python from scratch. Auth, the streaming price feed, a real favourite-drift lay strategy with exact triggers and stops, order placement, three non-optional safety rails, and the paper-trading discipline that has to happen before any real money goes on.

Updated 18 May 202614 min readAPI · Python · Tutorial

Scope — what you're building today

By the end of this tutorial you will have a Python bot that:

  • Logs into the Betfair Exchange API.
  • Streams live prices from UK horse racing markets.
  • Watches each market's favourite for a specific drift pattern in the 60 seconds before the off.
  • Posts a small lay bet when the pattern fires, with a stop-loss and take-profit attached.
  • Logs every action to a CSV for review.

This is the practical companion to Betfair API Getting Started, which we recommend reading first. The cluster pillar is Betfair Trading Software — Complete Reviews if you want the wider context.

The strategy itself — pre-off favourite drift — is one we cover manually in Trading the Favourite. We pick it because it has a measurable edge in some markets and a clean numeric trigger that a bot can detect.

Stack and prerequisites

  • Python 3.10 or later.
  • betfairlightweight (the de facto Python SDK).
  • A funded Betfair Exchange account with API access and your SSL certificate set up — see the previous post for the walkthrough.
  • A laptop running anywhere — even a Raspberry Pi will do for a single-strategy bot.

Install:

pip install betfairlightweight python-dotenv pandas

Project structure

bot/
├── .env # username, password, app key, cert paths
├── client.py # auth + session reuse
├── stream.py # streaming price listener
├── strategy.py # the rules
├── execution.py # order placement
├── safety.py # kill switch + stake limits
└── main.py # ties it together

Keeping concerns separated like this makes the bot debuggable. Bugs in strategy.py shouldn't be able to break safety.py.

Login and session handling

from dotenv import load_dotenv; import os, betfairlightweight
load_dotenv()
def client():
c = betfairlightweight.APIClient(
username=os.getenv('BF_USER'), password=os.getenv('BF_PASS'),
app_key=os.getenv('BF_KEY'), certs=os.getenv('BF_CERTS'))
c.login(); return c

Sessions expire after a period of inactivity; wrap calls in retry logic that re-authenticates on a 401.

Streaming live prices

Polling is fine for prototyping; the streaming API is required for any serious bot. Subscribe to UK horse racing win markets in the next two hours, with best-offer and traded-volume data.

listener = betfairlightweight.StreamListener()
stream = client.streaming.create_stream(listener=listener)
market_filter = {'eventTypeIds':['7'], 'marketCountries':['GB'],
'marketTypes':['WIN'], 'turnInPlayEnabled': True}
stream.subscribe_to_markets(market_filter=market_filter,
fields=['EX_BEST_OFFERS','EX_TRADED'])
stream.start()

You'll now receive push updates whenever any subscribed market's price changes. Your job is to consume those updates and decide whether to act.

The first strategy — favourite drift lay

The strategy in plain English: in the final 60 seconds before the off, if the favourite has risen by 2 ticks or more in the past 30 seconds while trading more than £500, post a small lay at the current LTP with a take-profit at LTP + 3 ticks and a stop at LTP - 2 ticks.

Strategy parameters
ScopeUK Win horse racing, favourite only
Time window60s > off > 5s
TriggerLTP up ≥ 2 ticks in 30s and 30s volume > £500
Stake£5 lay (start small)
Take profitLTP + 3 ticks
Stop lossLTP - 2 ticks
Hard cutoffOff time minus 5s — close all
def should_fire(market, fav_runner, now):
if not 5 < (market.market_definition.market_time - now).seconds < 60: return False
ticks_up = ticks_between(fav_runner.last_price_30s_ago, fav_runner.ltp)
if ticks_up < 2: return False
if fav_runner.volume_last_30s < 500: return False
return True

Execution and order management

def place_lay(client, market_id, sel_id, price, size):
order = {'selectionId': sel_id, 'side': 'LAY', 'orderType': 'LIMIT',
'limitOrder': {'size': size, 'price': price,
'persistenceType': 'LAPSE'}}
return client.betting.place_orders(market_id=market_id, instructions=[order])

Always check the response. Betfair will return a status: 'FAILURE' if a bet is rejected (insufficient funds, market suspended, price out of range). Don't assume success.

Safety rails that aren't optional

Three rails — together they have saved real money for real bots:

  1. Daily stake limit. Track total stake placed since midnight. Hard-stop the bot at, say, £100/day until you trust it.
  2. Per-market stake limit. Never have more than one open position per market. Reject any new order if you already have an unmatched bet there.
  3. Kill switch. A separate process watches a file or an HTTP endpoint. If you touch that switch, the bot stops accepting new orders and closes any open positions. We've heard horror stories from traders who couldn't stop their own bot.
The single most common bot disaster

A bug that places the same bet in a loop. Within seconds you can fill a market with £20 lays at progressively worse prices. Add a "max bets per minute per market" counter — and once you hit it, log a critical alert and stop.

Paper-trade for a week

Modify place_lay to log the bet to CSV instead of sending it to Betfair. Run the bot live against real markets for 5–7 trading sessions. Compare the CSV to what would have happened: did your bet get matched? At what price? Would it have hit stop or take-profit first? This is the only honest way to know whether the strategy works before risking capital.

Cross-reference: how to validate a trading system and our notes on how to validate a trading system.

Going live and scaling up

Once the paper-trade CSV is showing a positive expectancy across at least 100 trades, flip the switch in execution.py from "log" to "place". Keep the stake at £5 for the first week of live trading. Bank up only after you've seen 50+ real placed trades.

Scaling beyond the favourite-drift starter:

  • Add a second strategy (e.g. pre-race scalping, lay the draw).
  • Switch from betfairlightweight polling to the streaming API.
  • Move from a laptop to a VPS in London (lower latency to Betfair's data centre).
  • Consider whether you've crossed the premium charge threshold.

Adjacent reading: Betfair automation triggers and rules, the Guardian alternative, and the building Betfair bots overview.

Environment file (.env) — what goes in it

Never commit credentials to git. Use a .env file:

BF_USER=your.betfair.username
BF_PASS=your.betfair.password
BF_KEY=your_live_or_delay_app_key
BF_CERTS=/Users/you/certs

Add .env to .gitignore. If you ever accidentally commit it, immediately rotate your Betfair password and regenerate your app key.

Pinning dependencies

Use a requirements.txt with versions pinned. APIs change. Bots that worked in 2024 sometimes don't work in 2026 because a library updated. Pin everything.

betfairlightweight==2.20.4
python-dotenv==1.0.1
pandas==2.2.2

The tick-arithmetic helper you'll need

Betfair prices are not equally spaced. The tick size is 0.01 from 1.01–2.00, 0.02 from 2.00–3.00, 0.05 from 3.00–4.00, and so on. Adding "3 ticks" to 3.40 means 3.55, not 3.43. You'll write this helper exactly once and use it forever.

PRICE_INC = [
(2.00, 0.01), (3.00, 0.02), (4.00, 0.05),
(6.00, 0.10), (10.00, 0.20), (20.00, 0.50),
(30.00, 1.00), (50.00, 2.00), (100.00, 5.00),
(1000.00, 10.00),
]
def tick_step(price):
for ceiling, inc in PRICE_INC:
if price < ceiling: return inc
def add_ticks(price, n):
for _ in range(n): price = round(price + tick_step(price), 2)
return price

Every bot bug we've seen related to "the bot bet at the wrong price" traced back to either missing this function or implementing it wrong. Copy it once, test it carefully.

State management — keeping track of your own bets

Your bot needs to remember what it did. Even a simple bot has state:

  • Which markets is it watching?
  • Which markets has it already traded today?
  • What's the current open position per market?
  • What were the trigger conditions when it entered?

Two options. A SQLite database (overkill for one strategy, perfect once you have three). Or in-memory state with a periodic snapshot to JSON on disk (recoverable across crashes). For the first bot, JSON snapshots every 60 seconds is enough.

Measuring the bot's performance honestly

From day one, log every bet to a CSV with these columns:

  • Timestamp (UTC)
  • Market ID, market name, runner name
  • Side (BACK/LAY), price, stake
  • Trigger that fired
  • Bet ID returned by Betfair
  • Result (matched, unmatched, lapsed) — backfilled from listClearedOrders
  • P&L on settlement — backfilled

Two patterns to watch for:

  • Most of your "wins" are small, most of your "losses" are big. Your stop-loss is too loose.
  • Most trades close at the hard cutoff rather than at TP or SL. Your TP/SL ranges are too wide for the time available.

The discipline of weekly review is what eventually makes a profitable bot. We cover the broader testing discipline in how to validate a trading system.

Latency budget — where the milliseconds go

For a £20 stake at a 3.40 price, a missed tick is roughly £0.06. So every 100ms of latency you save is one or two more matched bets per session. The breakdown of a typical round-trip:

  • Stream update arrives: baseline.
  • Your bot parses and decides: 1–5ms (Python is fast enough).
  • HTTP call to Betfair API: 25–80ms from home broadband, 10–25ms from a London VPS.
  • Betfair processes: 5–15ms.
  • Confirmation back: mirror of the outbound.

Total: 60–200ms round-trip. Worth optimising? Yes, if you're scalping. No, if you're swing trading.

Running the bot in a container

Once the bot works, containerising it (Docker) makes it easier to deploy to a VPS, reproduce dev/prod, and isolate Python versions:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "main.py"]

Run as docker run --env-file .env --restart=unless-stopped your-bot. The restart flag is critical — if the bot crashes, you want it to come back up automatically.

Monitoring and alerts

Three minimum monitoring habits:

  • Daily P&L email or Telegram message. Roughly 10 minutes after the last UK racing of the day, summarise. Total trades, win rate, net £, biggest win, biggest loss.
  • Heartbeat ping. Every 60 seconds, the bot writes "alive" to a file with a timestamp. A second tiny script checks the file age and alerts you if it's stale.
  • Sanity bounds on per-market exposure. If your bot somehow has £200 open in one market, your alerting fires.

Failure modes seen in the wild

From our own experience and reader stories:

  1. Cert expired silently. SSL cert valid for 5 years, then suddenly your login fails. Generate certs valid for 5 years and put the expiry date in your calendar.
  2. Off-by-one in tick arithmetic. Bet at the wrong price. Loses small money over thousands of trades.
  3. Stake-size unit error. Confused £20 and £200. Loses lots of money in one trade.
  4. Forgetting to handle BET_TAKEN_OR_LAPSED. Bot retries at original price after the market moved, missing entries forever in fast markets.
  5. Timezone bug. Bot thinks UK racing is in your local time. Misses windows.
  6. Streaming disconnect with no reconnect. The connection dies, the bot stays "running" but receives no updates. Heartbeat catches this; trade-count monitoring catches this faster.

How the bot evolves over six months

Month 1: paper trading with the favourite-drift strategy. Discover the strategy has a small edge in some markets but loses in others.

Month 2: add a filter so the bot only runs on flat all-weather UK courses. Real money, £5 stakes.

Month 3: stakes raised to £10. Add a second strategy — a tennis set-end lay. Both running in parallel.

Month 4: refine the trigger thresholds based on logged data. The "2 ticks in 30s" trigger becomes "2.5 ticks in 40s with WoM < 0.8".

Month 5: graduate to a London VPS. Move to streaming-only data.

Month 6: portfolio of 3–4 strategies, £20 default stake, sensible monitoring. About now is when the bot has paid for the time you spent building it.

This is the realistic path. We've seen far more impressive trajectories and far less impressive ones. The variance is mostly in how much screen time the trader put in before automating.

UK gambling winnings are not subject to income tax. This applies to trading too in normal circumstances. Two things to know:

  • If trading is your primary income, HMRC may treat it as a business, which changes the tax position. Consult an accountant. We discuss the structure question in business structuring.
  • If you trip Betfair's premium charge threshold — see Betfair premium charge — your net P&L is reduced. Plan for this.

Where to keep learning

Aside from this site, useful external resources:

  • The official Betfair Developer Programme documentation.
  • The betfairlightweight GitHub repository for SDK reference.
  • The flumine framework if you want a higher-level abstraction over streaming + strategy + risk.
  • Our own community pieces: Betfair Discord communities and YouTube traders worth following.

Adjacent reading: Betfair API: Getting Started for the foundations; Bet Angel Guardian for the no-code alternative; automation triggers and rules for the conceptual model.

A reader's first bot — the actual lessons

One reader shared his diary of a 12-week bot project. The condensed version:

  • Week 1: got authentication working. "Surprisingly hard because of the SSL cert step."
  • Week 2: got polling prices working. Realised polling wouldn't scale.
  • Week 3: switched to streaming. Lost two days to a bug where his delta-merge code was overwriting the cached order book incorrectly.
  • Week 4: coded the favourite-drift strategy. Paper traded for a week.
  • Week 5: paper trade showed an edge. Went live at £2 stakes.
  • Week 6: realised the bot was placing bets at LTP rather than LTP+1 (a typo). Corrected.
  • Week 7-8: bumped stake to £5. Edge held.
  • Week 9: bug — the streaming connection dropped overnight, the bot stayed "running" but received no updates, missed all of Saturday's racing.
  • Week 10: added heartbeat monitoring. Slept better.
  • Week 11: moved to a London VPS. Latency down from 50ms to 14ms.
  • Week 12: running stably. Net for the 12 weeks: roughly £180 profit. Time invested: maybe 80 hours.

That works out to £2.25/hour. He didn't do it for the hourly rate — he did it because the bot now keeps running while he watches Netflix. The compounding piece is in scaling up and the realistic income piece is in trading income realistic numbers.

Anti-patterns to avoid in your code

  • Bare except clausestry: ... except: pass hides the bug that just cost you money. Always log the exception.
  • Hardcoded magic numbers — extract every threshold (2 ticks, 30 seconds, £500 volume) into a config file. You'll tune them often.
  • Mixing strategy and execution in one function — separate the "should I bet?" question from the "how do I place the bet?" question. Otherwise you can't paper-trade.
  • No retry logic on transient errors — but also no infinite retry on permanent errors. Use bounded retry with exponential backoff.
  • Logging only on success — log every decision, including the "don't fire" decisions. You'll need them later.

betfairlightweight vs flumine vs DIY

Three layers of abstraction to choose from:

  • Raw HTTP — write the JSON-RPC calls yourself. Maximum flexibility, maximum tedium. Only do this if you have a reason.
  • betfairlightweight — Python SDK that wraps the API. Good API coverage, good streaming support, no opinion on strategy structure. Recommended starting point.
  • flumine — strategy framework built on betfairlightweight. Provides the strategy loop, order management, paper trading, backtesting. Recommended once you've outgrown a single-script bot.

Start with betfairlightweight, switch to flumine when you have three or more strategies to manage.

When to stop tweaking and ship

The most common failure mode for first bots is endless tweaking on the paper-trade stage. The rule we use: if the paper trade shows positive expectancy across 100+ trades, ship it at the smallest live stake. Real-money sample size beats simulated sample size every time. The bug that paper trading didn't surface is the bug that live trading will surface in week one.