import pandas as pd from collections import deque ...
Created on: December 14, 2025
Answered using GPT-5.2 Thinking by Chat01
Created on: December 14, 2025
Answered using GPT-5.2 Thinking by Chat01
import pandas as pd
from collections import deque
from sympy import symbols, Eq, solve
from datetime import datetime, timedelta
league_thresholds = {
"Albania Superiore": (2.52, 2.73),
"Andorra Primera": (2.42, 2.75),
"Armenia Premier": (2.32, 3.20),
"Bosnia WWin Liga": (2.00, 3.20),
"Croatia HNL": (2.29, 3.25),
"Cyprus League": (2.25, 3.20),
"Cyprus Division 2": (2.46, 2.70),
"Czech Chance Liga": (2.25, 3.30),
"Denmark Superliga": (2.40, 3.00),
"Greece Super League": (2.36, 3.40),
"Greece Super League 2": (2.25, 3.30),
"Hungary OTP Bank Liga": (2.17, 3.40),
"Hungary Merkantil Bank Liga": (2.38, 3.30),
"Luxembourg BGL Ligue": (2.20, 3.00),
"Malta Premier League": (2.75, 2.60), # Обратите внимание, что здесь home > away
"Montenegro Prva Liga": (2.50, 3.20),
"North Macedonia 1 MFL": (2.13, 3.60),
"Slovakia Nike Liga": (2.30, 3.00),
"Slovenia Prva Liga": (2.47, 3.00),
"Algeria Ligue 2": (1.87, 5.00),
"Mexico Liga de Expansion MX": (2.00, 4.00),
"South Africa Premiership": (2.31, 3.45),
"Croatia Prva NL": (2.40, 2.80),
"India ISL": (2.17, 3.33),
"Indonesia Liga 1": (2.27, 3.45),
"Bulgaria Vtora liga": (2.27, 3.45),
"Zambia Super League": (2.47, 3.10),
"Romania Liga 3": (2.30, 2.94),
"Myanmar National League": (2.50, 2.50),
"Tanzania Ligi Kuu Bara": (2.20, 3.30),
"France Nacional": (2.32, 3.30),
"Nigeria NPFL": (1.50, 6.00),
"Turkey 1.Lig": (2.27, 3.20),
"Algeria Ligue 1": (2.05, 4.30),
"Italy Serie C - A": (2.38, 3.30),
"Italy Serie B": (2.46, 3.50),
"Algeria Ligue 2 - 21": (2.00, 3.20),
"England National League South": (2.38, 3.00),
"Brazil Paulista A4": (2.44, 3.00),
"Ethiopia": (3.12, 2.85),
"Spain LaLiga": (2.29, 3.61),
"Australia NPL": (2.22, 3.00),
"Bulgaria Vtora Liga": (2.08, 3.70),
"Italy Serie C - C": (2.40, 3.20),
"Spain Segunda": (2.11, 4.02),
"France Ligue 1": (2.30, 3.60),
"Brazil Serie A": (2.05, 4.00),
"Brazil Serie B": (2.05, 4.00),
"Brazil Serie C": (2.15, 3.70),
"Iceland Besta Delid": (2.12, 3.10),
"Australia NPL Northern": (2.47, 2.30),
"Norway Division 2": (2.08, 2.94),
"USA MLS": (2.27, 3.20),
"Argentina": (2.22, 3.80),
"Mexico": (2.08, 3.80),
"Bundesliga - 2": (2.16, 3.55),
"France - 2": (2.16, 3.55)
}
def clean_team_name(name):
if isinstance(name, str):
return ' '.join(name.strip().split())
return name
excel_file_path = r"C:\Users\User\Desktop\results\results.xlsx"
df = pd.read_excel(excel_file_path)
matches = []
for _, row in df.iterrows():
match = (
row[0], # Лига (колонка A)
row[1], # Дата (колонка B)
clean_team_name(row[2]), # Домашняя команда (колонка C)
clean_team_name(row[3]), # Гостевая команда (колонка D)
row[4], # Коэффициент 1 (колонка E)
row[5], # Коэффициент на ничью (колонка F)
row[6] # Коэффициент 2 (колонка G)
)
matches.append(match)
def find_chains(matches, start_team, end_team, min_depth=1, max_depth=4):
# Создание графа игр (по принципу «команда – список (сосед, матч)»)
graph = {}
for match in matches:
league, date, home_team, away_team, odds1, odds_draw, odds2 = match
textif home_team not in graph: graph[home_team] = [] if away_team not in graph: graph[away_team] = [] # Включаем в граф связь home_team -> (away_team, match) и наоборот graph[home_team].append((away_team, match)) graph[away_team].append((home_team, match)) # Поиск в ширину для нахождения всех цепочек from collections import deque chains = [] queue = deque([(start_team, [], set())]) while queue: current_team, path, visited = queue.popleft() if current_team == end_team and path: if min_depth <= len(path) <= max_depth: chains.append(path) continue if len(path) >= max_depth: continue for neighbor, match in graph.get(current_team, []): if match not in visited: new_visited = visited.copy() new_visited.add(match) queue.append((neighbor, path + [match], new_visited)) return chains
start_team = input("Введите название стартовой команды: ").strip()
end_team = input("Введите название команды назначения: ").strip()
chains = find_chains(matches, start_team, end_team)
unique_chains = []
seen_chains = set()
for chain in chains:
chain_tuple = tuple(chain)
if chain_tuple not in seen_chains:
seen_chains.add(chain_tuple)
unique_chains.append(chain)
chains = unique_chains
class TeamStrengthCalculator:
def init(self):
self.games = []
self.equations_method_1 = []
self.equations_method_2 = []
self.teams = set()
textdef add_games(self, games): """ games – список кортежей: (league, date, home_team, away_team, odds1, odds_draw, odds2) """ for g in games: league, date, home_team, away_team, home_odds, draw_odds, away_odds = g self.games.append({ 'league': league, 'date': date, 'home_team': clean_team_name(home_team), 'away_team': clean_team_name(away_team), 'home_odds': float(str(home_odds).replace(',', '.')), 'draw_odds': float(str(draw_odds).replace(',', '.')), 'away_odds': float(str(away_odds).replace(',', '.')) }) self.teams.add(clean_team_name(home_team)) self.teams.add(clean_team_name(away_team)) def calculate_strength_difference_method_1(self): """ Метод 1. Для каждого матча берём индивидуальные пороги (threshold_home, threshold_away) и формируем «уравнения» (строки), которые потом решаются в simplify_with_sympy. """ for game in self.games: league_name = game['league'] home_team = game['home_team'] away_team = game['away_team'] home_odds = game['home_odds'] away_odds = game['away_odds'] # Берём пороги для данной лиги (или по умолчанию 2.40 и 3.00) threshold_home, threshold_away = league_thresholds.get(league_name, (2.40, 3.00)) if home_odds < threshold_home and away_odds > threshold_away: # формируем "уравнение": "HomeTeam = AwayTeam + 0.123" home_strength = (100 / threshold_home) - (100 / home_odds) away_strength = (100 / away_odds) - (100 / threshold_away) strength_difference = (abs(home_strength) + abs(away_strength)) / 2 self.equations_method_1.append( f"{home_team} = {away_team} + {strength_difference:.3f}" ) elif home_odds > threshold_home and away_odds < threshold_away: home_strength = (100 / home_odds) - (100 / threshold_home) away_strength = (100 / threshold_away) - (100 / away_odds) strength_difference = (abs(home_strength) + abs(away_strength)) / 2 self.equations_method_1.append( f"{away_team} = {home_team} + {strength_difference:.3f}" ) else: # Игнорируем для метода 1, если не подошли под условия pass def calculate_strength_difference_method_2(self): """ Метод 2. Аналогично, но с другой логикой. """ for game in self.games: league_name = game['league'] home_team = game['home_team'] away_team = game['away_team'] home_odds = game['home_odds'] away_odds = game['away_odds'] threshold_home, threshold_away = league_thresholds.get(league_name, (2.40, 3.00)) if home_odds < away_odds: # хозяева считаются фаворитом if home_odds < threshold_home: home_strength = abs((100 / threshold_home) - (100 / home_odds)) self.equations_method_2.append( f"{home_team} = {away_team} + {home_strength:.3f}" ) else: home_strength = abs((100 / home_odds) - (100 / threshold_home)) self.equations_method_2.append( f"{away_team} = {home_team} + {home_strength:.3f}" ) else: # гости считаются фаворитом if away_odds < threshold_away: away_strength = abs((100 / threshold_away) - (100 / away_odds)) self.equations_method_2.append( f"{away_team} = {home_team} + {away_strength:.3f}" ) else: away_strength = abs((100 / away_odds) - (100 / threshold_away)) self.equations_method_2.append( f"{home_team} = {away_team} + {away_strength:.3f}" ) def simplify_with_sympy(self, start_team, end_team): """ Решаем сформированные «уравнения» и вычисляем разницу сил между start_team и end_team. В этом методе также берём пороги исходя из лиги первого матча в self.games (если в цепочке несколько лиг – используем первую). """ if not self.games: print("Нет матчей для расчета.") return # Лига первого матча (если цепочка состоит из нескольких лиг, берём первую) first_league = self.games[0]['league'] threshold_home, threshold_away = league_thresholds.get(first_league, (2.40, 3.00)) # Создаём символы для каждой команды team_symbols = {} for team in self.teams: if team == start_team: # Фиксируем силу стартовой команды = 0 team_symbols[team] = 0 else: safe_name = team.replace(' ', '_').replace('-', '_') team_symbols[team] = symbols(safe_name) # Преобразуем equations_method_1 в список уравнений SymPy sympy_equations_method_1 = [] for equation in self.equations_method_1: parts = equation.split('=') if len(parts) == 2 and '+' in parts[1]: left_team, right = parts[0].strip(), parts[1].strip() right_team, strength_diff = right.split('+') right_team = right_team.strip() strength_diff = float(strength_diff.strip()) left_sym = team_symbols[left_team] right_sym = team_symbols[right_team] sympy_equations_method_1.append(Eq(left_sym, right_sym + strength_diff)) # Преобразуем equations_method_2 в список уравнений SymPy sympy_equations_method_2 = [] for equation in self.equations_method_2: parts = equation.split('=') if len(parts) == 2 and '+' in parts[1]: left_team, right = parts[0].strip(), parts[1].strip() right_team, strength_diff = right.split('+') right_team = right_team.strip() strength_diff = float(strength_diff.strip()) left_sym = team_symbols[left_team] right_sym = team_symbols[right_team] sympy_equations_method_2.append(Eq(left_sym, right_sym + strength_diff)) # Формируем список символов (исключая те, что зафиксированы = 0) symbols_list = [sym for sym in team_symbols.values() if sym != 0] # Решение уравнений solution_1 = solve(sympy_equations_method_1, symbols_list, dict=True) if sympy_equations_method_1 else [] solution_2 = solve(sympy_equations_method_2, symbols_list, dict=True) if sympy_equations_method_2 else [] # Ищем символ для end_team end_symbol = team_symbols.get(end_team) final_results = [] # --- Обработка результатов метода 1 --- if solution_1 and end_symbol is not None: sol = solution_1[0] if end_symbol in sol: end_value = sol[end_symbol] try: end_value_numeric = float(end_value.evalf()) # сила start_team = 0, значит разница (start_team - end_team) = -end_value_numeric strength_diff = -end_value_numeric new_odds_start = 100 / ((100 / threshold_home) + strength_diff) new_odds_end = 100 / ((100 / threshold_home) - strength_diff) final_results.append( f"Method 1: {start_team} = {end_team} + {strength_diff:.3f}; " f"{start_team} odds = {new_odds_start:.3f}; {end_team} odds = {new_odds_end:.3f}" ) except (TypeError, ValueError): print(f"Не удалось вычислить численное значение силы {end_team} в методе 1.") else: print(f"Команда {end_team} не найдена в решении метода 1.") else: print("Нет решения для метода 1.") # --- Обработка результатов метода 2 --- if solution_2 and end_symbol is not None: sol = solution_2[0] if end_symbol in sol: end_value = sol[end_symbol] try: end_value_numeric = float(end_value.evalf()) strength_diff = -end_value_numeric new_odds_start = 100 / ((100 / threshold_home) + strength_diff) new_odds_end = 100 / ((100 / threshold_home) - strength_diff) final_results.append( f"Method 2: {start_team} = {end_team} + {strength_diff:.3f}; " f"{start_team} odds = {new_odds_start:.3f}; {end_team} odds = {new_odds_end:.3f}" ) except (TypeError, ValueError): print(f"Не удалось вычислить численное значение силы {end_team} в методе 2.") else: print(f"Команда {end_team} не найдена в решении метода 2.") else: print("Нет решения для метода 2.") # Вывод итоговых результатов if final_results: for result in final_results: print(result) else: print("Не удалось вычислить разницу в силе между командами.")
if chains:
# Группируем цепочки по длине
chain_groups = {}
for chain in chains:
chain_length = len(chain)
if chain_length not in chain_groups:
chain_groups[chain_length] = []
text# Вычисляем «среднюю дату» в цепочке (для сортировки от более новых к старым) dates = [] for match in chain: # match[1] – это дата raw_date = match[1] date_obj = None # Пытаемся аккуратно преобразовать к datetime if isinstance(raw_date, datetime): date_obj = raw_date elif isinstance(raw_date, (float, int)): # Считаем, что это Excel-формат дат (кол-во дней с 30.12.1899). try: date_obj = datetime(1899, 12, 30) + timedelta(days=int(raw_date)) except: date_obj = None elif isinstance(raw_date, str): # Проверяем, есть ли разделитель " - " try: if ' - ' in raw_date: date_part = raw_date.split(' - ')[0].strip() # "dd.mm.yyyy" date_obj = datetime.strptime(date_part, '%d.%m.%Y') else: # Может быть просто "dd.mm.yyyy" date_obj = datetime.strptime(raw_date.strip(), '%d.%m.%Y') except: date_obj = None if date_obj: dates.append(date_obj) else: print(f"Не удалось интерпретировать дату: {raw_date}") if dates: avg_timestamp = sum(d.timestamp() for d in dates) / len(dates) avg_date = datetime.fromtimestamp(avg_timestamp) chain_groups[chain_length].append({'chain': chain, 'avg_date': avg_date}) else: # Если не получилось распарсить ни одной даты, ставим минимальную для сортировки chain_groups[chain_length].append({'chain': chain, 'avg_date': datetime.min}) # Сортируем цепочки в каждой группе по средней дате (от более свежей к более старой) for chain_length in sorted(chain_groups.keys()): chain_infos = chain_groups[chain_length] chain_infos_sorted = sorted(chain_infos, key=lambda x: x['avg_date'], reverse=True) chain_groups[chain_length] = chain_infos_sorted # Выводим результаты chain_index = 1 for chain_length in sorted(chain_groups.keys()): print(f"\n=== Цепочки из {chain_length} WTP ===\n") chain_infos = chain_groups[chain_length] for chain_info in chain_infos: chain = chain_info['chain'] avg_date = chain_info['avg_date'] avg_date_str = avg_date.strftime('%d.%m.%Y') print(f"Цепочка {chain_index} (Средняя дата: {avg_date_str}):") # Собираем матчи и лиги games = [] used_leagues = set() for match in chain: league, date, home_team, away_team, odds1, odds_draw, odds2 = match # Упрощенный вывод каждого матча print(f"('{date}', '{home_team}', '{away_team}', {odds1}, {odds_draw}, {odds2})") games.append(match) used_leagues.add(league) print("\nИспользуемые лиги в цепочке и их пороги:") for lg in sorted(used_leagues): th_home, th_away = league_thresholds.get(lg, (2.40, 3.00)) print(f" Лига: {lg} → home={th_home}, away={th_away}") # Расчет и вывод методов calculator = TeamStrengthCalculator() calculator.add_games(games) calculator.calculate_strength_difference_method_1() calculator.calculate_strength_difference_method_2() calculator.simplify_with_sympy(start_team, end_team) print("\n" + "="*75 + "\n") chain_index += 1
else:
print("Не найдены WTP-цепочки между указанными командами.") изучи этот код и скажи есть ли способы лучше посчитать коэффициент между командами в будущей игре чем здесь
Да, можно. Текущий код — это скорее эвристика по “цепочкам” + попытка “протянуть” разницу сил через набор локальных уравнений из коэффициентов. Это работает иногда, но у него есть системные проблемы, из-за которых для прогноза коэфов будущей игры он почти всегда будет хуже, чем более “статистические” подходы.
Ты используешь только цепочки между двумя командами (max_depth=4).
Это отбрасывает 90% информации лиги. Оценки силы получаются “локальными” и зависят от того, нашлась ли удачная цепочка.
Пороги (threshold_home/threshold_away) — дискретная логика.
Команда чуть сильнее/слабее порога → формула резко меняется, хотя реальный эффект непрерывен.
Маржа букмекера не убирается.
Ты работаешь с “грязными” odds. Правильнее сначала переводить odds → вероятности и “снимать маржу” (devig).
Ничья почти не участвует в силе.
Хотя для футбола draw — огромная часть структуры рынка. Игнорирование odds_draw ухудшает прогноз.
Sympy solve на уравнениях вида A = B + k:
simplify_with_sympy() ты потом переводишь “силу” обратно в odds через порог первой лиги цепочки — это просто невалидно, если в цепочке разные лиги (и даже в одной лиге это сомнительно).Ниже — 3 практичных варианта, которые почти всегда дадут более стабильный и калиброванный результат, чем цепочки+пороги.
Идея: считаем, что рынок даёт нам “сигнал силы” в каждом матче, и из всех матчей лиги оцениваем рейтинги команд.
Шаги:
r_* — рейтинги, h — домашнее преимущество, k — масштаб.Почему это лучше твоего:
Для прогноза будущей игры:
diff = r_home - r_away + h,Если у тебя мало данных/хочется проще — отличный компромисс.
p1, px, p2.Плюсы:
Минусы:
Чтобы прогнозировать 1/X/2 корректно, сильнее всего работают модели, которые моделируют голы.
Базовая идея:
Голы ~ Пуассон (или бивариантный), далее считаем вероятности всех счётов → p1, px, p2 → odds.
Можно обучать:
p1, px, p2 (хуже, но всё равно лучше порогов).Плюсы:
Если ты пока не хочешь переписывать на рейтинги/пуассон, минимум что резко повысит качество:
log(p1/p2) вместо ветвлений threshold.E = p1 + 0.5*px).Если хочешь, я могу накидать готовую версию под твой формат Excel: