Agents
An Agent orchestrates the evaluate-place loop. Subclass BaseAgent, implement evaluate(), and the base class handles fetching, stake clamping, open-position limits, and error recovery.
Overview
| Class | Description |
|---|---|
BaseAgent | Abstract base class. Subclass and implement evaluate(). |
AgentConfig | Dataclass controlling bankroll, stake sizing, edge threshold, and risk limits. |
ValueAgent | Built-in example agent. Bets where offered odds imply positive EV vs the no-vig fair price. |
AgentConfig
Pass an AgentConfig to any agent constructor to control its behaviour. All fields have sensible defaults.
from opensport.agents.base import AgentConfig
config = AgentConfig(
bankroll=5_000.0,
min_edge_pct=0.03,
max_stake_pct=0.05,
max_open_positions=10,
sports_filter=["soccer", "nfl"],
dry_run=False,
)| Parameter | Default | Description |
|---|---|---|
bankroll | 1000.0 | Current balance, used for Kelly-based stake sizing. |
max_stake_pct | 0.05 | Hard cap: max 5% of bankroll per bet, regardless of Kelly output. |
min_edge_pct | 0.03 | Minimum positive edge required before placing (3% = 0.03). |
max_open_positions | 10 | Stop placing new bets when this many positions are open. |
sports_filter | [] | Empty list = all sports. Otherwise restrict to the listed slugs. |
dry_run | False | Evaluate and log but do not call executor.place(). |
BaseAgent
from opensport.agents.base import BaseAgent, AgentConfig
from opensport.core.event import Event
from opensport.core.odds import OddsSnapshot
from opensport.core.position import BetIntent
class MyAgent(BaseAgent):
def evaluate(self, event: Event, snapshot: OddsSnapshot) -> BetIntent | None:
# return a BetIntent to place a bet, or None to skip this event
...
agent = MyAgent(provider=provider, execution=sim, config=AgentConfig())
positions = agent.run(sport="soccer", limit=20)Constructor
| Parameter | Description |
|---|---|
provider | Any BaseProvider (or MultiProvider). |
execution | Any BaseExecutor (Simulator or live exchange). |
config | AgentConfig instance. Defaults to AgentConfig() if omitted. |
run()
Fetches scheduled events, calls evaluate() on each, clamps stakes to max_stake_pct, and calls executor.place(). Returns the list of positions placed.
| Parameter | Default | Description |
|---|---|---|
sport | None | Sport slug filter. Overrides config.sports_filter for this run. |
limit | None | Max events to evaluate. Useful in tests and demos. |
evaluate()
The one method you must implement. Called once per event. Return a BetIntent to place a bet or None to skip.
def evaluate(self, event: Event, snapshot: OddsSnapshot) -> BetIntent | None:
market = snapshot.market("winner")
if not market:
return None
# Your strategy logic here
return BetIntent(
event_id=event.id,
market_type="winner",
outcome_label="Home",
side=Side.BACK,
stake=50.0,
min_odds=2.0,
)Helper methods
| Method | Description |
|---|---|
placed_positions() | Returns all positions placed by this agent instance across all run() calls. |
ValueAgent
The built-in example agent. For each event it evaluates the match-winner market, strips the bookmaker's overround to find fair probabilities, and bets where the offered odds imply positive expected value. Stake is sized using fractional Kelly.
from opensport.agents.example import ValueAgent
agent = ValueAgent(
provider=provider,
execution=sim,
config=AgentConfig(bankroll=1_000.0, min_edge_pct=0.03),
market_type="winner", # which market to evaluate (default: "winner")
kelly_fraction=0.25, # quarter-Kelly stake sizing (default: 0.25)
)
positions = agent.run(sport="soccer")| Parameter | Default | Description |
|---|---|---|
market_type | "winner" | Which market slug to look for value in. |
kelly_fraction | 0.25 | Kelly multiplier. 0.25 = quarter-Kelly (more conservative). |
Strategy helper functions
Three pure functions are exported from opensport.agents.example for use in custom agents.
remove_vig(market)
Strips the bookmaker's overround by proportionally normalising implied probabilities. Returns a dict of outcome_label: fair_probability.
from opensport.agents.example import remove_vig
snap = provider.get_odds(event.id)
market = snap.market("winner")
fair = remove_vig(market)
# {"Home": 0.4762, "Draw": 0.2857, "Away": 0.2381}edge(offered_odds, fair_prob)
Calculates expected value as a fraction: (offered_odds × fair_prob) - 1. Positive = value bet, negative = bad value.
from opensport.agents.example import edge
e = edge(offered_odds=2.20, fair_prob=0.4762)
# 0.0476 → 4.76% edgekelly_stake(bankroll, edge_frac, odds, fraction)
Fractional Kelly criterion stake sizing.
from opensport.agents.example import kelly_stake
stake = kelly_stake(
bankroll=1_000.0,
edge_frac=0.0476, # 4.76% edge
odds=2.20,
fraction=0.25, # quarter-Kelly
)
# 27.50Building a custom agent
Subclass BaseAgent and implement evaluate(). Replace remove_vig with your own model to build a real strategy.
from opensport.agents.base import BaseAgent, AgentConfig
from opensport.agents.example import remove_vig, edge, kelly_stake
from opensport.core.event import Event
from opensport.core.odds import OddsSnapshot
from opensport.core.position import BetIntent, Side
from opensport.core.market import MarketType
class MyModelAgent(BaseAgent):
"""
Replace remove_vig with your own probability model.
"""
def evaluate(self, event: Event, snapshot: OddsSnapshot) -> BetIntent | None:
market = snapshot.market(MarketType.WINNER)
if not market:
return None
# Swap this for your model's probabilities
fair_probs = remove_vig(market)
best = None
for outcome in market.outcomes:
if not outcome.is_available:
continue
fair_p = fair_probs.get(outcome.label, 0.0)
e = edge(outcome.decimal_odds, fair_p)
if e >= self.config.min_edge_pct:
if best is None or e > best[1]:
best = (outcome, e)
if best is None:
return None
chosen, chosen_edge = best
stake = kelly_stake(
bankroll=self.config.bankroll,
edge_frac=chosen_edge,
odds=chosen.decimal_odds,
fraction=0.25,
)
if stake < 1.0:
return None
return BetIntent(
event_id=event.id,
market_type=MarketType.WINNER,
outcome_label=chosen.label,
side=Side.BACK,
stake=stake,
min_odds=chosen.decimal_odds,
)See the Execution reference for how positions are settled, and the MCP Server to expose your agent's data to any LLM host.