OSopensport.dev
Reference

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

ClassDescription
BaseAgentAbstract base class. Subclass and implement evaluate().
AgentConfigDataclass controlling bankroll, stake sizing, edge threshold, and risk limits.
ValueAgentBuilt-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,
)
ParameterDefaultDescription
bankroll1000.0Current balance, used for Kelly-based stake sizing.
max_stake_pct0.05Hard cap: max 5% of bankroll per bet, regardless of Kelly output.
min_edge_pct0.03Minimum positive edge required before placing (3% = 0.03).
max_open_positions10Stop placing new bets when this many positions are open.
sports_filter[]Empty list = all sports. Otherwise restrict to the listed slugs.
dry_runFalseEvaluate 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

ParameterDescription
providerAny BaseProvider (or MultiProvider).
executionAny BaseExecutor (Simulator or live exchange).
configAgentConfig 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.

ParameterDefaultDescription
sportNoneSport slug filter. Overrides config.sports_filter for this run.
limitNoneMax 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

MethodDescription
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")
ParameterDefaultDescription
market_type"winner"Which market slug to look for value in.
kelly_fraction0.25Kelly 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% edge

kelly_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.50

Building 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.