passer au contenu principal
paste
bin
.ca
type · paste · share
⌘
K
Famille
La famille bin
pastebin.ca
pôle
Share text and code with expiry and privacy controls.
imagebin.ca
Upload and share images with direct links.
filebin.ca
Drop a file and get a shareable link.
notebin.ca
Write Markdown notes with durable links.
turl.ca
Short, reputation-checked links.
attn.ca
Notifications and alerts for your services.
voicebin.ca
Record and share short voice clips.
dnsbin.ca
Inspect DNS and debug records.
Docs
Se connecter
?
← retour au collage
›
Modifier / dupliquer
root
#3KFwz38F8y
profile / public
nouvelle version
par @glad-crow-1664
créé 21 days ago
aucune expiration
24.7 KB
syntaxe:
python
Vos changements créent un nouveau collage lié à celui-ci — l’original reste intact.
nouvelle version
Vos changements créent un nouveau collage lié à celui-ci — l’original reste intact.
Titre (facultatif)
Nom de fichier
Syntaxe
python
text
bash
c
cpp
css
diff
dockerfile
go
html
ini
java
javascript
json
kotlin
lua
makefile
markdown
nginx
php
python
ruby
rust
shellscript
sql
swift
toml
typescript
xml
yaml
Visibilité
Afficher dans le fil utilisateur
Accès
public
Expire
7 jours
10 min
1 heure
1 jour
7 jours
30 jours
90 jours
personnalisé…
Expiration personnalisée
Note de changement
(facultatif)
Créer une nouvelle version
Annuler
Collez ou tapez…
# core.py import numpy as np from scipy.stats import poisson from league_modules import module_registry, MODULE_EXECUTION_ORDER from rule_engine import RuleEngine, Context class FootballPredictor: def __init__(self, **kwargs): self.params = kwargs defaults = { 'home_control_rate': 0.50, 'away_press_rate': 0.25, 'home_defense_loss_rate': 0.15, 'away_counter_attacks': 2.0, 'home_stadium_condition': 'normal', 'is_old_team_return': False, 'is_top_club': False, 'var_controversy': False, 'big_ball_rate_home': 0.50, 'big_ball_rate_away': 0.50, 'home_team': 'Home', 'away_team': 'Away', 'home_shots_avg': 0, 'away_shots_avg': 0, 'home_possession': 0.50, 'away_possession': 0.50, 'odds_home_handicap': 0, 'odds_draw_handicap': 0, 'odds_away_handicap': 0, } for key, val in defaults.items(): if key not in self.params: self.params[key] = val self.league_config = kwargs.get('league_config', {}) self.modules_cfg = self.league_config.get('modules', {}) self.top_clubs = self.league_config.get('top_clubs', []) self.odds_h = self.params['odds_home'] self.odds_d = self.params['odds_draw'] self.odds_a = self.params['odds_away'] self.round = self.params['season_round'] self.context = Context() self._init_context_from_config() self.result = {} def _init_context_from_config(self): cfg = self.league_config.get('modules', {}) self.context.set('rdi_threshold', cfg.get('rdi', {}).get('threshold', 0.30)) self.context.set('uat_threshold', cfg.get('uat', {}).get('threshold', 0.75)) self.context.set('obd_threshold', cfg.get('odds_deviation', {}).get('threshold', 0.15)) self.context.set('ds_threshold', cfg.get('dynamic_pressure', {}).get('threshold', 420)) self.context.set('acf_threshold', cfg.get('adversity', {}).get('threshold', 1.00)) self.context.set('q_attack_boost', 1.0) self.context.set('fpa_boost', 1.0) self.context.set('kpa_boost', 1.0) self.context.set('tcm_boost', 1.0) self.context.set('dta_boost', 1.0) self.context.set('pwl_boost', 1.0) self.context.set('splitter_boost', 1.0) self.context.set('wfl_triggered', False) self.context.set('orc_triggered', False) self.context.set('btp_triggered', False) self.context.set('hfa_triggered', False) self.context.set('obd_triggered', False) self.context.set('drc_triggered', False) self.context.set('rdi_triggered', False) self.context.set('uat_triggered', False) self.context.set('score_corrections', {}) def _calc_match_probabilities(self, xg_h, xg_a, max_goals=6): ph = [poisson.pmf(k, xg_h) for k in range(max_goals+1)] pa = [poisson.pmf(k, xg_a) for k in range(max_goals+1)] score_probs = {} win_h = draw = win_a = 0.0 for i in range(max_goals+1): for j in range(max_goals+1): p = ph[i] * pa[j] score_probs[(i,j)] = p if i > j: win_h += p elif i == j: draw += p else: win_a += p total = win_h + draw + win_a if total > 0: win_h /= total draw /= total win_a /= total best_score = max(score_probs, key=score_probs.get) best_prob = score_probs[best_score] sorted_scores = sorted(score_probs.items(), key=lambda x: -x[1]) return { 'win_h': win_h, 'draw': draw, 'win_a': win_a, 'best_score': best_score, 'best_prob': best_prob, 'score_probs': score_probs, 'sorted_scores': sorted_scores } def _generate_score_matrix(self, sorted_scores): core = sorted_scores[0][0] if sorted_scores else (0,0) defend = [] for (i,j), p in sorted_scores: if i+j <= 2: defend.append((i,j)) if len(defend) >= 3: break if not defend: defend = [(0,0), (1,0), (1,1)] open_attack_cfg = self.league_config.get('open_attack', {}) threshold = open_attack_cfg.get('threshold', 0.50) big_ball_h = self.params.get('big_ball_rate_home', 0.50) big_ball_a = self.params.get('big_ball_rate_away', 0.50) open_attack = (big_ball_h > threshold and big_ball_a > threshold) total_xg = self.result.get('xg_h_after_A', 0) + self.result.get('xg_a_after_A', 0) if not open_attack and total_xg > 2.8: open_attack = True self.result['_debug']['open_attack_forced'] = True attack = [] if open_attack: for (i,j), p in sorted_scores: if i+j >= 3: attack.append((i,j)) if len(attack) >= 3: break return {'core': core, 'defend': defend, 'attack': attack, 'open_attack': open_attack} def _generate_strategy(self): win_h = self.result['prob_win_h'] draw = self.result['prob_draw'] win_a = self.result['prob_win_a'] strategy = {} if win_h > 0.5: strategy['Safe'] = f"Home Win ({self.odds_h:.2f})" elif draw > 0.4: strategy['Safe'] = f"Draw ({self.odds_d:.2f})" elif win_a > 0.5: strategy['Safe'] = f"Away Win ({self.odds_a:.2f})" else: strategy['Safe'] = "Double chance: Draw/Away" if self.odds_h < 1.30: strategy['Balanced'] = "Asian Handicap -1 / -1.5" elif 1.90 <= self.odds_h <= 2.05 and self.result.get('ewc_trigger', False): strategy['Balanced'] = "Home Win + Under 2.5" elif 2.00 <= self.odds_h <= 2.30: strategy['Balanced'] = "Home Win or Draw" else: strategy['Balanced'] = "Home Win / Draw" total_xg = self.result['xg_h_after_A'] + self.result['xg_a_after_A'] if total_xg < 2.0: strategy['Goals'] = "1, 2" elif total_xg < 3.0: strategy['Goals'] = "2, 3" else: strategy['Goals'] = "3, 4" half1 = self._calc_match_probabilities(self.result['xg_h_half1'], self.result['xg_a_half1']) half2 = self._calc_match_probabilities(self.result['xg_h_half2'], self.result['xg_a_half2']) h1_state = 'W' if half1['win_h'] > 0.4 else ('D' if half1['draw'] > 0.4 else 'L') h2_state = 'W' if half2['win_h'] > 0.4 else ('D' if half2['draw'] > 0.4 else 'L') strategy['HT/FT'] = f"{h1_state}/{h2_state}" self.result['strategy'] = strategy def _generate_tags(self): tags = [] if self.result.get('ewc_trigger', False): tags.append('Economic Win') if self.result.get('dsr_iron_draw', False): tags.append('Draw Alert') if self.odds_h < 1.30: tags.append('Deep Odds') if self.params['home_stadium_condition'] != 'normal': tags.append('Home Discount') if self.result.get('vtd_trap', False): tags.append('Trap Warning') if self.result.get('bpr_triggered', False): tags.append('Counter Risk') if self.result.get('open_attack', False): tags.append('Attack Mode') if self.context.get('hfa_triggered'): tags.append('HFA Boost') if self.context.get('btp_triggered'): tags.append('Clutch Alert') if self.context.get('obd_triggered'): tags.append('Odds Deviation') if self.context.get('orc_triggered'): tags.append('ORC Alert') if self.context.get('drc_triggered'): tags.append('DRC Alert') if self.context.get('wfl_triggered'): tags.append('WFL Signal') if not tags: tags.append('Regular') self.result['tags'] = tags def _apply_injury_adjustment(self, xg_h, xg_a): home_injuries = self.params.get('home_injuries', 0) away_injuries = self.params.get('away_injuries', 0) home_key_missing = self.params.get('home_key_missing', 0) away_key_missing = self.params.get('away_key_missing', 0) injury_cfg = self.league_config.get('injury', {}) home_penalty = (home_injuries * injury_cfg.get('home_injury_penalty', 0.04) + home_key_missing * injury_cfg.get('home_key_penalty', 0.08)) away_penalty = (away_injuries * injury_cfg.get('away_injury_penalty', 0.04) + away_key_missing * injury_cfg.get('away_key_penalty', 0.08)) min_xg = injury_cfg.get('min_xg_after_injury', 0.25) xg_h = max(min_xg, xg_h - home_penalty) xg_a = max(min_xg, xg_a - away_penalty) if home_penalty > 0 or away_penalty > 0: self.result['_debug']['injury_penalty_h'] = round(home_penalty, 3) self.result['_debug']['injury_penalty_a'] = round(away_penalty, 3) self.result['_debug']['xg_h_before_injury'] = round(xg_h + home_penalty, 3) self.result['_debug']['xg_a_before_injury'] = round(xg_a + away_penalty, 3) return xg_h, xg_a def _apply_handicap_correction(self, score_probs, sorted_scores): handicap_line = self.params.get('handicap_line', 0) odds_home_h = self.params.get('odds_home_handicap', 1.90) odds_away_h = self.params.get('odds_away_handicap', 1.90) if handicap_line == 0 or odds_home_h == 0 or odds_away_h == 0: return score_probs, sorted_scores prob_home_cover = 1 / odds_home_h if odds_home_h > 0 else 0 prob_away_cover = 1 / odds_away_h if odds_away_h > 0 else 0 total = prob_home_cover + prob_away_cover if total > 0: prob_home_cover /= total prob_away_cover /= total if handicap_line < 0: target_goals = abs(handicap_line) if prob_home_cover > 0.55: for (i, j), p in sorted_scores[:15]: if i - j >= int(target_goals) + 1: score_probs[(i, j)] *= 1.15 self.result['handicap_signal'] = 'Home Cover (Big Win Expected)' else: for (i, j), p in sorted_scores[:15]: if 1 <= i - j <= 2: score_probs[(i, j)] *= 1.10 self.result['handicap_signal'] = 'Home Win but Lose Cover (Small Win)' elif handicap_line > 0: if prob_away_cover > 0.55: for (i, j), p in sorted_scores[:15]: if j - i >= int(handicap_line) + 1: score_probs[(i, j)] *= 1.15 self.result['handicap_signal'] = 'Away Cover (Big Win Expected)' else: for (i, j), p in sorted_scores[:15]: if 1 <= j - i <= 2: score_probs[(i, j)] *= 1.10 self.result['handicap_signal'] = 'Away Win but Lose Cover (Small Win)' new_sorted = sorted(score_probs.items(), key=lambda x: -x[1]) return score_probs, new_sorted # ==================== AB 线通用评分(修复版) ==================== def _apply_ab_scoring(self, xg_h, xg_a): """ 通用 AB 线评分调度器(从配置读取权重,不写死任何联赛逻辑) 基于 A 线(基本面)和 B 线(盘口)的交叉验证修正 xG """ ab_cfg = self.league_config.get('ab_scoring', {}) if not ab_cfg.get('active', False): return xg_h, xg_a # 1. 计算 A 线(基本面)评分 a_cfg = ab_cfg.get('a_line', {}) home_power = self.params.get('home_power', 50) away_power = self.params.get('away_power', 50) # 基础战力值归一化 (50->0.5, 100->1.0) a_score_h = 0.5 + (home_power - 50) / 100 a_score_a = 0.5 + (away_power - 50) / 100 # 伤停修正(从配置读取惩罚系数) injuries_penalty = a_cfg.get('injuries_penalty', 0.05) home_inj = self.params.get('home_injuries', 0) * injuries_penalty away_inj = self.params.get('away_injuries', 0) * injuries_penalty a_score_h = max(0.1, a_score_h - home_inj) a_score_a = max(0.1, a_score_a - away_inj) # 战意修正(从配置读取) mot_boost = a_cfg.get('motivation_boost', 1.1) if self.params.get('motivation_home', 0.5) > 0.6: a_score_h = a_score_h * mot_boost if self.params.get('motivation_away', 0.5) > 0.6: a_score_a = a_score_a * mot_boost # 2. 计算 B 线(盘口)评分 b_cfg = ab_cfg.get('b_line', {}) # 理论胜率(基于 ELO 或 power) elo_h = self.params.get('elo_home', 1500) elo_a = self.params.get('elo_away', 1500) if elo_h == 1500 and elo_a == 1500: # 如果没有 ELO,用 power 映射(50→1300, 100→1700) elo_h = 1300 + (home_power - 50) * 8 elo_a = 1300 + (away_power - 50) * 8 theo_prob_h = 1 / (1 + 10 ** ((elo_a - elo_h) / 400)) theo_prob_a = 1 - theo_prob_h # 市场隐含概率(从赔率提取) implied_h = 1 / self.odds_h if self.odds_h > 0 else 0.5 implied_a = 1 / self.odds_a if self.odds_a > 0 else 0.5 # B 线评分 = 1 - 偏差(偏差越小,评分越高) b_score_h = 1 - min(1, abs(implied_h - theo_prob_h) * 2) b_score_a = 1 - min(1, abs(implied_a - theo_prob_a) * 2) # 3. 交叉验证与置信度计算 val_cfg = ab_cfg.get('validation', {}) resonance_threshold = val_cfg.get('resonance_threshold', 0.1) divergence_threshold = val_cfg.get('divergence_threshold', 0.3) resonance_boost = val_cfg.get('resonance_boost', 1.2) divergence_penalty = val_cfg.get('divergence_penalty', 0.7) # 计算 A/B 线对主胜和客胜评分的平均一致性 diff_h = abs(a_score_h - b_score_h) diff_a = abs(a_score_a - b_score_a) avg_diff = (diff_h + diff_a) / 2 if avg_diff <= resonance_threshold: confidence_boost = resonance_boost self.result['ab_validation'] = 'resonance' elif avg_diff >= divergence_threshold: confidence_boost = divergence_penalty self.result['ab_validation'] = 'divergence' else: confidence_boost = 1.0 self.result['ab_validation'] = 'neutral' # 4. 将修正系数应用到 xG(修复版) if confidence_boost != 1.0: if confidence_boost > 1.0: # 共振:双方信心都提升 if implied_h > theo_prob_h: # 市场更看好主队 → 主队xG提升,客队适度提升 xg_h = xg_h * confidence_boost xg_a = xg_a * (1 + (confidence_boost - 1) * 0.5) else: # 市场更看好客队 → 客队xG提升,主队适度提升 xg_a = xg_a * confidence_boost xg_h = xg_h * (1 + (confidence_boost - 1) * 0.5) else: # 背离:双方信心都降低(用同一个惩罚系数,而不是一方被放大) # 原来的 xg / confidence_boost(即 xg / 0.7)会放大 xG,这是 bug # 修正为双方都乘以 confidence_boost(即都降低),只是降低幅度不同 if implied_h > theo_prob_h: # 市场更看好主队,但模型与市场背离 → 主队xG降低,客队降得更多 xg_h = xg_h * confidence_boost # 客队 × (confidence_boost ^ 1.5),比主队降得更多 xg_a = xg_a * (confidence_boost * confidence_boost) else: # 市场更看好客队,但模型与市场背离 → 客队xG降低,主队降得更多 xg_a = xg_a * confidence_boost # 主队 × (confidence_boost ^ 1.5),比客队降得更多 xg_h = xg_h * (confidence_boost * confidence_boost) self.result['ab_confidence_boost'] = confidence_boost self.result['ab_scoring_active'] = True return xg_h, xg_a # ==================== 核心 predict 方法 ==================== def predict(self): self.result['_debug'] = {} xg_h = self.params['base_xg_home'] xg_a = self.params['base_xg_away'] self.result['_debug']['xg_h_initial'] = round(xg_h, 3) self.result['_debug']['xg_a_initial'] = round(xg_a, 3) # ----- 从配置读取 xG 调整 ----- xg_adj = self.league_config.get('xg_adjustments', {}) boost = xg_adj.get('default_boost', 1.10) boost_cfg = xg_adj.get('boost_conditions', {}) odds_h = self.odds_h odds_a = self.odds_a league = self.params.get('league', '') if odds_h < 1.40: boost += boost_cfg.get('deep_odds', 0.12) if odds_a > 4.00: boost += boost_cfg.get('big_away_odds', 0.10) if self.params.get('big_ball_rate_home', 0.5) > 0.55 and self.params.get('big_ball_rate_away', 0.5) > 0.55: boost += boost_cfg.get('both_attacking', 0.12) if self.params.get('relegation_zone', False): boost += boost_cfg.get('relegation_zone', 0.10) if self.params.get('first_leg_lead', 0) >= 2: boost += boost_cfg.get('second_leg_conservative', -0.20) if self.params.get('forward_missing', 0) >= 2: boost += boost_cfg.get('forward_missing_penalty', -0.08) xg_h = xg_h * xg_adj.get('home_boost_factor', 1.0) xg_a = xg_a * xg_adj.get('away_penalty_factor', 1.0) if self.params.get('relegation_zone', False) and xg_adj.get('relegation_home_boost', 0): xg_h = xg_h + xg_adj.get('relegation_home_boost', 0) xg_h = max(xg_h, xg_adj.get('relegation_min_xg', 1.0)) boost = max(xg_adj.get('boost_min', 0.85), min(xg_adj.get('boost_max', 1.60), boost)) xg_h = xg_h * boost xg_a = xg_a * boost self.result['_debug']['offensive_boost'] = round(boost, 3) self.result['_debug']['boost_reasons'] = [] if odds_h < 1.40: self.result['_debug']['boost_reasons'].append('deep_odds') if odds_a > 4.00: self.result['_debug']['boost_reasons'].append('big_away_odds') if self.params.get('big_ball_rate_home', 0.5) > 0.55 and self.params.get('big_ball_rate_away', 0.5) > 0.55: self.result['_debug']['boost_reasons'].append('both_attacking') if self.params.get('first_leg_lead', 0) >= 2: self.result['_debug']['boost_reasons'].append('second_leg_conservative') # 西甲深盘额外加成 if league == 'LaLiga' and self.params.get('is_top_club', False) and odds_h < 1.40: la_liga_extra = xg_adj.get('la_liga_deep_odds_extra', 0.08) boost += la_liga_extra self.result['_debug']['la_liga_extra_boost'] = la_liga_extra xg_max = xg_adj.get('xg_max', 5.0) xg_h = min(xg_h, xg_max) xg_a = min(xg_a, xg_max) # 西甲豪门主场保底 la_liga_floor = xg_adj.get('la_liga_home_floor', 2.2) if league == 'LaLiga' and self.params.get('is_top_club', False) and odds_h < 1.40: xg_h = max(xg_h, la_liga_floor) self.result['_debug']['la_liga_home_floor_applied'] = True self.result['_debug']['la_liga_home_floor_value'] = la_liga_floor # 伤病调整 xg_h, xg_a = self._apply_injury_adjustment(xg_h, xg_a) # ===== 🆕 AB 线评分(通用交叉验证) ===== xg_h, xg_a = self._apply_ab_scoring(xg_h, xg_a) self.result['_debug']['xg_h_after_injury'] = round(xg_h, 3) self.result['_debug']['xg_a_after_injury'] = round(xg_a, 3) # 通用主场保底(巴西杯等) if self.league_config.get('home_floor', {}).get('active', False): floor_cfg = self.league_config.get('home_floor', {}) if self.params.get('is_top_club', False) and self.params.get('home_win_rate', 0) > floor_cfg.get('win_rate_threshold', 0.70): xg_h = max(xg_h, floor_cfg.get('min_xg', 1.5)) self.result['_debug']['home_floor_applied'] = True # ===== 模块执行(含沙特新模块) ===== pre_modules = ['wfl', 'rdi', 'uat'] for module_name in pre_modules: if module_name in module_registry: module_func = module_registry[module_name] xg_h, xg_a = module_func(self, xg_h, xg_a) self.context.set('current_xg_h', xg_h) self.context.set('current_xg_a', xg_a) RuleEngine.apply_rules(self.context) for module_name in MODULE_EXECUTION_ORDER: if module_name in module_registry and module_name not in pre_modules: module_cfg = self.modules_cfg.get(module_name, {}) if module_cfg.get('active', True): module_func = module_registry[module_name] #print(f"▶️ 执行模块: {module_name}") xg_h, xg_a = module_func(self, xg_h, xg_a) self.context.set('current_xg_h', xg_h) self.context.set('current_xg_a', xg_a) RuleEngine.apply_rules(self.context) # ===== 🆕 全局 xG 上限保护(防止极端强弱对话爆炸) ===== xg_global_cap = xg_adj.get('xg_global_cap', 5.0) if xg_h > xg_global_cap: self.result['_debug']['xg_h_capped'] = round(xg_h, 3) xg_h = xg_global_cap if xg_a > xg_global_cap: self.result['_debug']['xg_a_capped'] = round(xg_a, 3) xg_a = xg_global_cap self.result['xg_h_after_A'] = xg_h self.result['xg_a_after_A'] = xg_a self.result['_debug']['xg_h_final'] = round(xg_h, 3) self.result['_debug']['xg_a_final'] = round(xg_a, 3) # 半全场划分 tds_config = self.modules_cfg.get('tds', {}) if tds_config.get('active', False): early = tds_config.get('early', [0.40, 0.60]) mid = tds_config.get('mid', [0.45, 0.55]) deep = tds_config.get('deep', [0.35, 0.65]) if self.round <= 3: half_h, half_a = early[0], early[1] elif self.odds_h < 1.30: half_h, half_a = deep[0], deep[1] else: half_h, half_a = mid[0], mid[1] else: half_h, half_a = 0.45, 0.55 h_h1, h_h2 = xg_h * half_h, xg_h * half_a a_h1, a_h2 = xg_a * half_h, xg_a * half_a if self.params.get('var_controversy', False): a_h2 *= 1.05 self.result['xg_h_half1'] = h_h1 self.result['xg_h_half2'] = h_h2 self.result['xg_a_half1'] = a_h1 self.result['xg_a_half2'] = a_h2 # 概率计算 probs = self._calc_match_probabilities(xg_h, xg_a) self.result['core_prob'] = probs['best_prob'] score_corrections = self.context.get('score_corrections', {}) for score_key, weight in score_corrections.items(): if score_key in probs['score_probs']: probs['score_probs'][score_key] *= weight probs['sorted_scores'] = sorted(probs['score_probs'].items(), key=lambda x: -x[1]) win_h, draw, win_a = probs['win_h'], probs['draw'], probs['win_a'] if self.result.get('dsr_iron_draw', False): draw *= 1.20 if self.context.get('q_attack_boost', 1.0) != 1.0: win_h *= self.context.get('q_attack_boost', 1.0) if self.context.get('fpa_boost', 1.0) != 1.0: win_h *= self.context.get('fpa_boost', 1.0) total = win_h + draw + win_a if total > 0: win_h /= total draw /= total win_a /= total draw_cap = self.modules_cfg.get('dsr', {}).get('draw_cap', 0.35) if draw > draw_cap: draw = draw_cap total = win_h + win_a if total > 0: win_h = win_h / total * (1 - draw_cap) win_a = win_a / total * (1 - draw_cap) self.result['prob_win_h'] = win_h self.result['prob_draw'] = draw self.result['prob_win_a'] = win_a if self.params.get('handicap_line', 0) != 0: probs['score_probs'], probs['sorted_scores'] = self._apply_handicap_correction( probs['score_probs'], probs['sorted_scores'] ) matrix = self._generate_score_matrix(probs['sorted_scores']) self.result['score_core'] = matrix['core'] self.result['score_defend'] = matrix['defend'] self.result['score_attack'] = matrix['attack'] self.result['open_attack'] = matrix['open_attack'] self._generate_strategy() self._generate_tags() return self.result