How to build a system for early detection of team dominance?

What is team dominance in a match and what metrics to consider it by

Team dominance in a match is not just about the score on the scoreboard. A team may lead 1:0 but play defensively, or it may completely control the game at 0:0, creating more chances and applying constant pressure. For analytics and betting, it is important gameplay, not just the scoring advantage, so the system’s task is to translate the course of the match into a set of objective numbers.

At the data level, dominance is described by a set of metrics available in the sports API. In football and hockey, this usually includes possession of the ball/puck, number of shots and attempts, shots on target, dangerous moments, corners, entries into the final third of the field, number of passes, and key actions. In basketball and esports, the focus shifts to the number of attacks, possession tempo, effective shots, kills/objects on the map, etc. Through the endpoint /v2/{sportSlug}/partidos of the service API de eventos deportivos you can obtain aggregated match statistics in the field estadísticasDelPartido and based on it build a numerical dominance index.

A practical approach is to collect key indicators and combine them into a single score, for example Dominance Index. For football, such features can be used as:

  • ball possession (key ballPossession);
  • total shots and shots on target (totalDisparosALaPortería, disparosALaPortería);
  • big goal-scoring chances (granOportunidadCreada, granOportunidadMarcada);
  • entries into the final third and touches in the penalty area (finalThirdEntries, touchesInOppBox);
  • percentage of won duels (duelWonPercent);
  • accurate passes and long passes (accuratePasses, accurateLongBalls);
  • number of corners (tiros de esquina).

Each metric can be assigned a weight considering its impact on goals and results. For example, a shot on target is more important than a simple pass, and a major goal event weighs more than possession. In the early definition of dominance, it is critical to consider not only absolute values but also the difference by teams, as well as the current minute of the match (field minutoDelPartidoActual): a lead of 5 shots in the first 15 minutes carries a different meaning than the same lead in the 80th minute.

Below is a simplified example of a function that takes a fragment estadísticasDelPartido from the API response and calculates the dominance index for the hosts:

def compute_dominance(statistics, side="home"):
    # side: "home" или "away"
    s = {"home": 0.0, "away": 0.0}
    def add_score(key, weight, positive=True):
        for period in statistics:  # ALL, 1ST, 2ND
            for group in period.get("groups", []):
                for item in group.get("statisticsItems", []):
                    if item.get("key") == key:
                        h = item.get("homeValue") or 0
                        a = item.get("awayValue") or 0
                        diff = (h - a) if positive else (a - h)
                        s["home"] += diff * weight
                        s["away"] -= diff * weight
    add_score("ballPossession", 0.5)
    add_score("shotsOnGoal", 1.5)
    add_score("bigChanceCreated", 2.0)
    add_score("cornerKicks", 0.7)
    add_score("duelWonPercent", 0.3)
    return s[side]

In real products, weights and the set of metrics are selected based on historical data and A/B tests, but the principle remains the same: data from the API is translated into a single index that allows for a decision on who is truly dominating at a specific moment in time.

Which sports statistics APIs to use for analyzing team dominance

For the early dominance detection system to work reliably, it needs structured and timely data. This data is provided by the REST API of the service api-sport.ru, which covers football, hockey, basketball, tennis, table tennis, esports, and other disciplines. For each of them, schedules, match statuses, scores, detailed statistics, live events, and, importantly for betting, current and starting bookmaker odds are available in the block oddsBase.

Basic set of endpoints for analyzing team dominance:

  • GET /v2/sport — list of supported sports with their slug;
  • GET /v2/{sportSlug}/matches?status=inprogress — list of current live matches by sport with fields minutoDelPartidoActual, estadísticasDelPartido, eventosEnVivo, oddsBase;
  • GET /v2/{sportSlug}/matches/{matchId} — detailed data on a specific match, including team lineups and advanced statistics;
  • GET /v2/{sportSlug}/matches/{matchId}/events — a complete chronological list of match events, useful for building time series.

At the odds level, the block oddsBase returns markets with current and starting values (decimal, initialDecimal) and an indicator of change (cambiar). A sharp drop in the odds for a team’s victory often correlates with its tactical and gameplay advantage, so combining statistics and line dynamics provides a more accurate signal of dominance.

Below is an example of an API request to get a list of all current football matches with statistics. The authorization uses a header Autorización with a key that can be obtained at tu cuenta personal en api-sport.ru:

import requests
API_KEY = "ВАШ_API_КЛЮЧ"
BASE_URL = "https://api.api-sport.ru"
headers = {
    "Authorization": API_KEY,
}
params = {
    "status": "inprogress",  # только лайв-матчи
}
response = requests.get(
    f"{BASE_URL}/v2/football/matches",
    headers=headers,
    params=params,
)
response.raise_for_status()
data = response.json()
for match in data.get("matches", []):
    print(
        match["id"],
        match["homeTeam"]["name"],
        "vs",
        match["awayTeam"]["name"],
        "мин.", match.get("currentMatchMinute"),
    )

This call can be adapted for any sport by simply replacing fútbol with the desired sportSlug. Then the system retrieves estadísticasDelPartido, calculates the dominance index, and based on that makes decisions: highlight the match to the trader, send a signal to the betting model, or adjust limits in the line.

How to collect and store data from sports event APIs for online analytics

The early dominance detection system is sensitive to delays and data losses. It is important not only to correctly calculate the index at the current moment but also to have a history of the match with intervals of several seconds or minutes. For this, a small layer of collection and storage is built on top of the REST API, which converts responses /v2/{sportSlug}/partidos и /v2/{sportSlug}/matches/{matchId} into analysis-friendly structures.

A typical pipeline looks like this:

  • Match dispatcher periodically (for example, every 5–10 seconds) requests the current list of live matches for the required sports.
  • The statistics service retrieves detailed data for each match, including estadísticasDelPartido, eventosEnVivo, oddsBase.
  • Storage saves «snapshots» of the match: minute, score, key stats, odds. This can be a relational database, a specialized time-series database (TimescaleDB, ClickHouse), or even an in-memory storage for short-term analysis.
  • Analytics service subscribes to updates and recalculates the dominance index with each change in statistics.

In upcoming updates, the API is planned to support WebSocket, which will allow abandoning frequent polling and switching to event streaming. However, it is already possible to build a reliable framework on REST, adhering to request frequency limits and optimizing the list of monitored matches.

Below is an example of a simplified collector in Python that periodically updates statistics for all live football matches and stores basic information in a local dictionary. In production, a database, cache, or message broker is used instead of a dictionary.

import time
import requests
API_KEY = "ВАШ_API_КЛЮЧ"
BASE_URL = "https://api.api-sport.ru"
headers = {"Authorization": API_KEY}
state = {}  # match_id -> list of snapshots

def fetch_live_matches():
    resp = requests.get(
        f"{BASE_URL}/v2/football/matches",
        headers=headers,
        params={"status": "inprogress"},
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json().get("matches", [])

def fetch_match_details(match_id):
    resp = requests.get(
        f"{BASE_URL}/v2/football/matches/{match_id}",
        headers=headers,
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json()

while True:
    for match in fetch_live_matches():
        mid = match["id"]
        details = fetch_match_details(mid)
        snapshot = {
            "minute": details.get("currentMatchMinute"),
            "homeScore": details["homeScore"]["current"],
            "awayScore": details["awayScore"]["current"],
            "stats": details.get("matchStatistics", []),
            "odds": details.get("oddsBase", []),
        }
        state.setdefault(mid, []).append(snapshot)
    time.sleep(10)  # интервал опроса

Such a framework allows for quickly adding any analytical layer: from simple heuristics to machine learning models. It is important to think through the storage scheme in advance: which fields are estadísticasDelPartido always stored, which are aggregated, and which are kept only as the latest values to minimize data volume.

Algorithms and models for early detection of team dominance based on live data

After the data from the API is consistently received and stored, the next step is to choose an algorithm that will translate «raw» metrics into a clear business signal: which team is dominant and how early this can be asserted. Both simple rules and machine learning models trained on historical matches, extracted through /v2/{sportSlug}/partidos from past seasons, are used here.

The basic level — heuristic indices. They combine possession, shots, moments, and other metrics into a single score (see the example of the function compute_dominance above). Such indices are easy to interpret and quickly adjust for specific tournaments or sports. The dominance threshold can be selected based on historical data: for example, consider a team dominant if its index exceeds the threshold and is maintained for more than N minutes.

The next step — statistical and ML models. Based on matches from the API, a dataset can be assembled where the features will be the values estadísticasDelPartido and dynamics of odds oddsBase in the first 10-20 minutes, and the target variable will be the final score or the fact that the team created more quality chances during the match. Logistic regression, gradient boosting, or a simple neural network are trained on this data to assess the probability that one side is currently dominant or will be dominant in the coming minutes.

The roadmap of the service api-sport.ru announces the appearance of ready-made AI tools on top of sports data. This will make it even easier to connect pre-trained models to their systems, combining them with the bookmaker’s internal algorithms and risk models.

Below is an example of a simple rule in Python that uses the dominance index and the current minute of the match to generate an early signal:

DOM_THRESHOLD = 3.0
MINUTE_FROM = 10
MINUTE_TO = 35

def early_dominance_signal(match_details):
    minute = match_details.get("currentMatchMinute") or 0
    stats = match_details.get("matchStatistics", [])
    if not (MINUTE_FROM <= minute <= MINUTE_TO):
        return None
    home_score = compute_dominance(stats, side="home")
    away_score = compute_dominance(stats, side="away")
    if home_score - away_score >= DOM_THRESHOLD:
        return {"team": "home", "score": home_score - away_score}
    if away_score - home_score >= DOM_THRESHOLD:
        return {"team": "away", "score": away_score - home_score}
    return None

In production, such rules are supplemented with filters by tournaments, stages, match status, and context of odds. In practice, the best results come from hybrid approaches: the ML model assesses the probability of dominance, while strict business constraints formulated as simple rules are imposed on top of it.

How to set up a notification system for team dominance based on API data

The score of the dominance index is of little use by itself if it does not lead to action. A trader, risk manager, or automated strategy needs timely highlighting of matches where one of the teams clearly takes the initiative. Therefore, a flexible notification system is built on top of the analytical core, which reacts to changes in the data received from /v2/{sportSlug}/partidos и /v2/{sportSlug}/matches/{matchId}.

Architecturally, it looks like this:

  • The index calculation service subscribes to new «slices» of the match from the storage or receives them directly from the API.
  • With each recalculation, the decision-making block, is triggered, which checks the dominance thresholds, the dynamics of the odds oddsBase and additional conditions (the minute of the match, the tournament, the score).
  • If the conditions are met, an alert event is generated and sent to the appropriate channel: trader interface, messenger, webhook to your internal system.

From a practical point of view, it is important to avoid «noise»: not to notify about every fluctuation in statistics, but to react to sustained dominance. For this, time smoothing is used (the index above the threshold for several consecutive minutes) and context consideration — for example, not sending notifications at the end of the match if the line is already closed.

Below is an example of code that, on top of the previously described function early_dominance_signal generates and processes alert events. In a real system, instead of print and a stub send_alert message queues and external delivery services are used.

ALERT_MIN_STREAK = 2  # минимум подряд срабатываний
streaks = {}  # match_id -> {"team": str, "count": int}

def process_match_for_alert(match_details):
    match_id = match_details["id"]
    signal = early_dominance_signal(match_details)
    if not signal:
        streaks.pop(match_id, None)
        return
    prev = streaks.get(match_id)
    if prev and prev["team"] == signal["team"]:
        prev["count"] += 1
    else:
        streaks[match_id] = {"team": signal["team"], "count": 1}
    cur = streaks[match_id]
    if cur["count"] >= ALERT_MIN_STREAK:
        send_alert(match_details, cur["team"], signal["score"])
        streaks.pop(match_id, None)

def send_alert(match_details, team, score_gap):
    home = match_details["homeTeam"]["name"]
    away = match_details["awayTeam"]["name"]
    minute = match_details.get("currentMatchMinute")
    print(
        f"ALERT: доминирование {team} в матче {home} vs {away}, "
        f"мин. {minute}, индекс {score_gap:.2f}",
    )

In practice, such a block easily integrates into the existing risk systems of a betting company or analytical dashboards. Thanks to the structured data from the API, you can flexibly adjust thresholds for different leagues, markets, and limits, and with the emergence of WebSocket support in the future, also reduce delays between the event on the field and the signal within your internal tools.

Example of implementing an early detection system for team dominance in Python

Below is a coherent example in Python that demonstrates the full cycle: obtaining live matches via the API, loading their detailed statistics, calculating the dominance index, and outputting early signals for each match. This template can be used as a basis for your own service, wrapping it in a background worker, Docker container, or microservice as part of your infrastructure.

The example relies on the same endpoints described above: /v2/fútbol/partidos и /v2/fútbol/partidos/{matchId}. For other sports, it is enough to replace fútbol with the desired sportSlug. Don’t forget to substitute a real API key obtained in tu cuenta personal en api-sport.ru.

import time
import requests
API_KEY = "ВАШ_API_КЛЮЧ"
BASE_URL = "https://api.api-sport.ru"
SPORT = "football"  # football, basketball, ice-hockey, tennis и т.д.
headers = {"Authorization": API_KEY}

def get_live_matches():
    resp = requests.get(
        f"{BASE_URL}/v2/{SPORT}/matches",
        headers=headers,
        params={"status": "inprogress"},
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json().get("matches", [])

def get_match_details(match_id):
    resp = requests.get(
        f"{BASE_URL}/v2/{SPORT}/matches/{match_id}",
        headers=headers,
        timeout=5,
    )
    resp.raise_for_status()
    return resp.json()

def loop():
    while True:
        matches = get_live_matches()
        if not matches:
            print("Нет лайв-матчей, ожидание...")
            time.sleep(15)
            continue
        for short in matches:
            match_id = short["id"]
            details = get_match_details(match_id)
            signal = early_dominance_signal(details)
            if signal:
                team = "home" if signal["team"] == "home" else "away"
                home = details["homeTeam"]["name"]
                away = details["awayTeam"]["name"]
                minute = details.get("currentMatchMinute")
                print(
                    f"РАННИЙ СИГНАЛ: {home} vs {away}, мин. {minute}, "
                    f"доминирует {team}, индекс {signal['score']:.2f}",
                )
        time.sleep(10)

if __name__ == "__main__":
    loop()

In a real product, this code is supplemented with response caching, logging, network error handling, accounting for odds from the field oddsBase, integration with storage and notification systems. But even this script shows how quickly a working prototype of a dominance early detection system can be built, relying solely on the data provided by the service. API de eventos deportivos.