It's clear that you've been working at generic Python quality, and from that perspective this code is half-decent. Where it falls over is Numpy use.
In one sense, well-written Numpy code doesn't look very much like what we imagine to be Pythonic code. Your dataclass must go away, and your "single_" method must go away. The operations from "single_" must be pulled out to "mean_" and vectorised over the number of trials. References to the built-in math and random libraries will go away. With this done, you can run the code in much less time, and/or run many more trials.
Suggested
import numpy as np
from numpy.random import default_rng
MATCH_LENGTH = 95
TRIALS = 100_000
rand = default_rng()
def mean_game_state(
home_implied_goals: float,
away_implied_goals: float,
match_length_minutes: int = MATCH_LENGTH,
trials: int = TRIALS,
) -> tuple[float, float, float]:
"""
Given match length in minutes and implied goals for home and away teams,
calculates for how many minutes per match in average home team will be ahead,
there will be a draw and away team will be ahead.
"""
implied_goals = np.array((home_implied_goals, away_implied_goals))
# Probability to score in a given minute
goals_per_min = implied_goals / match_length_minutes
# For every minute in a game, 1 if a team scored in that minute, 0 otherwise
outcomes = rand.uniform(size=(trials, match_length_minutes, 2))
goals_by_minute = outcomes < goals_per_min
# How many goals a team scored by a particular minute of the game
home_cumulative_goals, away_cumulative_goals = goals_by_minute.cumsum(axis=1).T
diff = home_cumulative_goals - away_cumulative_goals
home_ahead = np.count_nonzero(diff > 0, axis=0)
away_ahead = np.count_nonzero(diff < 0, axis=0)
draw = np.count_nonzero(diff == 0, axis=0)
return home_ahead.mean(), draw.mean(), away_ahead.mean()
if __name__ == '__main__':
print(mean_game_state(home_implied_goals=2.15, away_implied_goals=1.20))