#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 统一彩票分析引擎 v2.0 ====================== 双色球/大乐透 使用完全相同的6阶段模型链路,仅参数按彩种适配。 设计原则: 1. 同一引擎,参数化适配(front_count, front_max, back_count, back_max) 2. 6阶段串行链路:泊松→伽马拟合→贝叶斯后验→K-Means→ILP约束→回测优化 3. 6类约束条件统一:奇偶/大小/和值/连号/冷热/三区 4. 回测机制:≥50期验证 + 权重自动优化 5. 数据量:≥200期历史数据 彩种参数: SSQ: front=6(1~33), back=1(1~16) DLT: front=5(1~35), back=2(1~12) """ import json, math, os, datetime, hashlib, random, sys from collections import Counter, defaultdict BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.join(BASE_DIR, "data") OUTPUT_DIR = os.path.join(BASE_DIR, "model_output") os.makedirs(OUTPUT_DIR, exist_ok=True) try: import numpy as np except ImportError: np = None # ================================================================ # 彩种配置 # ================================================================ LOTTERY_CONFIGS = { "ssq": { "name": "双色球", "front_count": 6, # 前区选球数 "front_max": 33, # 前区最大号码 "back_count": 1, # 后区选球数 "back_max": 16, # 后区最大号码 "draw_days": [2, 4, 7], # 周二、四、日 "draw_time": "21:15", "source": "500.com", "history_file": "ssq_history.json", "analysis_file": "analysis_ssq.json", "model_file": "model_ssq.json", # 6类约束(默认值,回测优化后会调整) "constraints": { "odd_even": {"min_odd": 2, "max_odd": 4}, # 奇偶比 "big_small": {"min_big": 2, "max_big": 4}, # 大小比(前区>front_max/2为大) "sum_range": {"min_sum": 70, "max_sum": 130}, # 和值区间 "consecutive": {"min_consec": 0, "max_consec": 2}, # 连号个数 "hot_cold": {"min_hot": 2, "max_hot": 4}, # 热号个数 "zone": {"min_zone": 1, "max_zone": 3}, # 三区分布 }, }, "dlt": { "name": "大乐透", "front_count": 5, # 前区选球数 "front_max": 35, # 前区最大号码 "back_count": 2, # 后区选球数 "back_max": 12, # 后区最大号码 "draw_days": [1, 3, 6], # 周一、三、六 "draw_time": "20:25", "source": "中国体育彩票", "history_file": "dlt_history.json", "analysis_file": "analysis_dlt.json", "model_file": "model_dlt.json", "constraints": { "odd_even": {"min_odd": 1, "max_odd": 4}, "big_small": {"min_big": 1, "max_big": 4}, "sum_range": {"min_sum": 55, "max_sum": 130}, "consecutive": {"min_consec": 0, "max_consec": 2}, "hot_cold": {"min_hot": 1, "max_hot": 4}, "zone": {"min_zone": 1, "max_zone": 3}, }, }, } # ================================================================ # 纯数学工具(无外部依赖) # ================================================================ def gamma_log(x): """Stirling近似 Gamma函数对数""" if x <= 0: return 0 if x < 0.5: return math.log(math.pi / math.sin(math.pi * x)) - gamma_log(1 - x) x -= 1 g = 7 c = [0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7] t = x + g + 0.5 s = c[0] for i in range(1, g + 2): s += c[i] / (x + i) return 0.5 * math.log(2 * math.pi) + (x + 0.5) * math.log(t) - t + math.log(s) def gamma_pdf(x, alpha, beta): """伽马分布概率密度""" if x <= 0 or alpha <= 0 or beta <= 0: return 0 return math.exp(alpha * math.log(beta) - gamma_log(alpha) + (alpha - 1) * math.log(x) - beta * x) def gamma_fit_ks(data, max_iter=100): """伽马分布参数估计(矩估计+KS检验)""" if len(data) < 5: return None, None, 1.0 n = len(data) mean = sum(data) / n var = sum((x - mean) ** 2 for x in data) / n if var <= 0 or mean <= 0: return None, None, 1.0 alpha = mean * mean / var beta = mean / var # KS统计量 sorted_data = sorted(data) max_d = 0 for i, x in enumerate(sorted_data): empirical = (i + 1) / n theoretical = gamma_cdf(x, alpha, beta) max_d = max(max_d, abs(empirical - theoretical)) # 近似p值(简单阈值) critical = 1.36 / math.sqrt(n) # 5%显著性水平 p_value = max(0, 1 - max_d / critical) if critical > 0 else 0 return alpha, beta, p_value def gamma_cdf(x, alpha, beta): """伽马分布CDF(数值积分近似)""" if x <= 0: return 0 # Simpson积分 n = 100 h = x / n s = gamma_pdf(0.001, alpha, beta) + gamma_pdf(x, alpha, beta) for i in range(1, n, 2): s += 4 * gamma_pdf(i * h, alpha, beta) for i in range(2, n, 2): s += 2 * gamma_pdf(i * h, alpha, beta) return min(1.0, s * h / 3) def poisson_pmf(k, lam): """泊松概率质量函数""" if lam <= 0: return 0 return math.exp(k * math.log(lam) - lam - gamma_log(k + 1)) def ks_test(data, cdf_func, *params): """KS检验""" n = len(data) if n < 5: return 1.0 sorted_data = sorted(data) max_d = 0 for i, x in enumerate(sorted_data): empirical = (i + 1) / n theoretical = cdf_func(x, *params) max_d = max(max_d, abs(empirical - theoretical)) critical = 1.36 / math.sqrt(n) return max(0, 1 - max_d / critical) if critical > 0 else 0 def normalize(scores): """归一化""" total = sum(scores.values()) if total == 0: return {k: 1.0 / len(scores) for k in scores} return {k: v / total for k, v in scores.items()} # ================================================================ # 统一6阶段引擎 # ================================================================ class UnifiedEngine: """统一彩票分析引擎 — 双色球/大乐透完全相同的模型链路""" def __init__(self, lottery_type): self.lt = lottery_type self.cfg = LOTTERY_CONFIGS[lottery_type] self.history = self._load_history() self.N = len(self.history) self.model = {} # 模型参数存档 def _load_history(self): """加载历史数据""" path = os.path.join(DATA_DIR, self.cfg["history_file"]) if not os.path.exists(path): print(f"[错误] 历史数据文件不存在: {path}") return [] data = json.load(open(path, "r", encoding="utf-8")) records = [] for r in data: fronts = sorted([int(x) for x in r["reds"]]) backs = sorted([int(x) for x in r["blue"]]) if isinstance(r["blue"], list) else [int(r["blue"])] if (len(fronts) == self.cfg["front_count"] and all(1 <= n <= self.cfg["front_max"] for n in fronts) and len(backs) == self.cfg["back_count"] and all(1 <= n <= self.cfg["back_max"] for n in backs)): records.append({ "issue": r["issue"], "fronts": fronts, "backs": backs, "date": r.get("date", "") }) records.sort(key=lambda x: int(x["issue"])) return records # ================================================================ # 阶段1:数据清洗与基础描述统计 # ================================================================ def phase1_descriptive_stats(self): """阶段1:泊松频次 + 遗漏统计""" fc = self.cfg["front_count"] fm = self.cfg["front_max"] bc = self.cfg["back_count"] bm = self.cfg["back_max"] # 前区频次 all_fronts = [] for r in self.history: all_fronts.extend(r["fronts"]) front_freq = Counter(all_fronts) front_theoretical = fc / fm # 每个号码理论出现率 front_stats = {} for n in range(1, fm + 1): freq = front_freq.get(n, 0) actual_rate = freq / self.N front_stats[n] = { "freq": freq, "actual_rate": actual_rate, "theoretical_rate": front_theoretical, "deviation": actual_rate - front_theoretical, } # 前区遗漏 front_last_seen = {n: 0 for n in range(1, fm + 1)} front_miss_stats = {n: {"avg_miss": 0, "max_miss": 0, "min_miss": 999, "last_miss": 0, "appear_count": 0} for n in range(1, fm + 1)} for n in range(1, fm + 1): gaps = [] last = -1 for i, r in enumerate(self.history): if n in r["fronts"]: if last >= 0: gaps.append(i - last) last = i front_miss_stats[n]["appear_count"] += 1 if gaps: front_miss_stats[n]["avg_miss"] = sum(gaps) / len(gaps) front_miss_stats[n]["max_miss"] = max(gaps) front_miss_stats[n]["min_miss"] = min(gaps) front_miss_stats[n]["last_miss"] = self.N - 1 - last if last >= 0 else self.N # 后区频次 all_backs = [] for r in self.history: all_backs.extend(r["backs"]) back_freq = Counter(all_backs) back_theoretical = bc / bm back_stats = {} for n in range(1, bm + 1): freq = back_freq.get(n, 0) actual_rate = freq / self.N back_stats[n] = { "freq": freq, "actual_rate": actual_rate, "theoretical_rate": back_theoretical, "deviation": actual_rate - back_theoretical, } # 后区遗漏 back_last_seen = {n: 0 for n in range(1, bm + 1)} back_miss_stats = {n: {"avg_miss": 0, "max_miss": 0, "min_miss": 999, "last_miss": 0, "appear_count": 0} for n in range(1, bm + 1)} for n in range(1, bm + 1): gaps = [] last = -1 for i, r in enumerate(self.history): if n in r["backs"]: if last >= 0: gaps.append(i - last) last = i back_miss_stats[n]["appear_count"] += 1 if gaps: back_miss_stats[n]["avg_miss"] = sum(gaps) / len(gaps) back_miss_stats[n]["max_miss"] = max(gaps) back_miss_stats[n]["min_miss"] = min(gaps) back_miss_stats[n]["last_miss"] = self.N - 1 - last if last >= 0 else self.N # 泊松参数 front_lam = self.N * fc / fm back_lam = self.N * bc / bm self.model["phase1"] = { "front_freq": {str(k): v for k, v in front_stats.items()}, "back_freq": {str(k): v for k, v in back_stats.items()}, "front_miss": {str(k): v for k, v in front_miss_stats.items()}, "back_miss": {str(k): v for k, v in back_miss_stats.items()}, "poisson_lambda_front": front_lam, "poisson_lambda_back": back_lam, } return self.model["phase1"] # ================================================================ # 阶段2:伽马分布拟合 # ================================================================ def phase2_distribution_fitting(self): """阶段2:伽马分布拟合遗漏周期""" fm = self.cfg["front_max"] bm = self.cfg["back_max"] # 前区伽马拟合 front_gamma = {} front_pass = 0 for n in range(1, fm + 1): miss = self.model["phase1"]["front_miss"][str(n)] data = [miss["avg_miss"], miss["max_miss"], miss["last_miss"]] data = [max(0.1, x) for x in data if x > 0] if len(data) >= 3: alpha, beta, p = gamma_fit_ks(data) if alpha and p > 0.05: front_pass += 1 front_gamma[str(n)] = {"alpha": alpha, "beta": beta, "p_value": p} else: front_gamma[str(n)] = {"alpha": None, "beta": None, "p_value": 0} # 后区伽马拟合 back_gamma = {} back_pass = 0 for n in range(1, bm + 1): miss = self.model["phase1"]["back_miss"][str(n)] data = [miss["avg_miss"], miss["max_miss"], miss["last_miss"]] data = [max(0.1, x) for x in data if x > 0] if len(data) >= 3: alpha, beta, p = gamma_fit_ks(data) if alpha and p > 0.05: back_pass += 1 back_gamma[str(n)] = {"alpha": alpha, "beta": beta, "p_value": p} else: back_gamma[str(n)] = {"alpha": None, "beta": None, "p_value": 0} front_rate = front_pass / fm if fm > 0 else 0 back_rate = back_pass / bm if bm > 0 else 0 self.model["phase2"] = { "front_gamma": front_gamma, "back_gamma": back_gamma, "front_pass_rate": front_rate, "back_pass_rate": back_rate, } return self.model["phase2"] # ================================================================ # 阶段3:贝叶斯后验概率 # ================================================================ def phase3_bayesian_posterior(self): """阶段3:贝叶斯条件概率校正""" fm = self.cfg["front_max"] bm = self.cfg["back_max"] fc = self.cfg["front_count"] # 前区后验 front_posterior = {} for n in range(1, fm + 1): fs = self.model["phase1"]["front_freq"][str(n)] fg = self.model["phase2"]["front_gamma"].get(str(n), {}) freq_score = fs["actual_rate"] miss_score = min(1.0, fs["deviation"] + 0.5) # 偏差越大,后验越高 gamma_score = 0.5 if fg.get("alpha") and fg.get("p_value", 0) > 0.05: gamma_score = min(1.0, fg["alpha"] / 5) # 贝叶斯:先验(泊松) × 似然(伽马) → 后验 posterior = 0.5 * freq_score + 0.3 * miss_score + 0.2 * gamma_score front_posterior[str(n)] = posterior # 归一化 total_fp = sum(front_posterior.values()) front_posterior = {k: v / total_fp for k, v in front_posterior.items()} # 后区后验 back_posterior = {} for n in range(1, bm + 1): bs = self.model["phase1"]["back_freq"][str(n)] bg = self.model["phase2"]["back_gamma"].get(str(n), {}) freq_score = bs["actual_rate"] miss_score = min(1.0, bs["deviation"] + 0.5) gamma_score = 0.5 if bg.get("alpha") and bg.get("p_value", 0) > 0.05: gamma_score = min(1.0, bg["alpha"] / 3) posterior = 0.5 * freq_score + 0.3 * miss_score + 0.2 * gamma_score back_posterior[str(n)] = posterior total_bp = sum(back_posterior.values()) back_posterior = {k: v / total_bp for k, v in back_posterior.items()} self.model["phase3"] = { "front_posterior": front_posterior, "back_posterior": back_posterior, "weights": {"w1_freq": 0.5, "w2_miss": 0.3, "w3_gamma": 0.2}, } return self.model["phase3"] # ================================================================ # 阶段4:K-Means聚类降维 # ================================================================ def phase4_clustering(self): """阶段4:K-Means聚类降维""" fm = self.cfg["front_max"] posterior = self.model["phase3"]["front_posterior"] # 简化K-Means:按后验概率分3簇 scores = [(n, posterior[str(n)]) for n in range(1, fm + 1)] scores.sort(key=lambda x: -x[1]) n_hot = max(5, fm // 4) # 热号簇 n_warm = max(5, fm // 3) # 温号簇 # 剩余为冷号簇 hot = [s[0] for s in scores[:n_hot]] warm = [s[0] for s in scores[n_hot:n_hot + n_warm]] cold = [s[0] for s in scores[n_hot + n_warm:]] self.model["phase4"] = { "hot": hot, "warm": warm, "cold": cold, "n_hot": n_hot, "n_warm": n_warm, "n_cold": len(cold), } return self.model["phase4"] # ================================================================ # 阶段5:ILP约束优化求解 # ================================================================ def phase5_ilp_solve(self, count=5): """阶段5:约束优化求解(遗传模拟)""" fm = self.cfg["front_max"] fc = self.cfg["front_count"] bm = self.cfg["back_max"] bc = self.cfg["back_count"] constraints = self.cfg["constraints"] # 构建历史已开组合(避免重复推荐) historical = set() for r in self.history: historical.add(tuple(r["fronts"])) # 综合得分 posterior = self.model["phase3"]["front_posterior"] hot_set = set(self.model["phase4"]["hot"]) def front_score(combo): return sum(posterior[str(n)] for n in combo) def check_constraints(combo): odd = sum(1 for n in combo if n % 2 == 1) if not (constraints["odd_even"]["min_odd"] <= odd <= constraints["odd_even"]["max_odd"]): return False big = sum(1 for n in combo if n > fm / 2) if not (constraints["big_small"]["min_big"] <= big <= constraints["big_small"]["max_big"]): return False s = sum(combo) if not (constraints["sum_range"]["min_sum"] <= s <= constraints["sum_range"]["max_sum"]): return False consec = sum(1 for i in range(len(combo) - 1) if combo[i + 1] - combo[i] == 1) if not (constraints["consecutive"]["min_consec"] <= consec <= constraints["consecutive"]["max_consec"]): return False hot = sum(1 for n in combo if n in hot_set) if not (constraints["hot_cold"]["min_hot"] <= hot <= constraints["hot_cold"]["max_hot"]): return False # 三区分布 z1 = sum(1 for n in combo if 1 <= n <= fm // 3) z2 = sum(1 for n in combo if fm // 3 < n <= 2 * fm // 3) z3 = sum(1 for n in combo if n > 2 * fm // 3) if not all(constraints["zone"]["min_zone"] <= z <= constraints["zone"]["max_zone"] for z in [z1, z2, z3]): return False if tuple(combo) in historical: return False return True # 随机搜索 + 约束过滤 seed = hashlib.md5(("unified_%s_%s" % (self.lt, self.history[-1]["issue"])).encode()).hexdigest() rng = random.Random(seed) best = [] for _ in range(50000): weights = np.array([posterior[str(n)] for n in range(1, fm + 1)]) weights = weights / weights.sum() selected = np.random.choice(range(1, fm + 1), size=fc, replace=False, p=weights) combo = sorted(selected.tolist()) if check_constraints(combo): score = front_score(combo) if len(best) < count * 3 or score > best[-1][0]: best.append((score, combo)) best.sort(key=lambda x: -x[0]) if len(best) > count * 3: best = best[:count * 3] # 去重取top seen = set() results = [] for score, combo in best: if tuple(combo) not in seen: seen.add(tuple(combo)) results.append((score, combo)) if len(results) >= count: break # 后区推荐 back_posterior = self.model["phase3"]["back_posterior"] back_ranked = sorted(back_posterior.items(), key=lambda x: -float(x[1]))[:bc + 2] self.model["phase5"] = { "front_combos": [(s, c) for s, c in results], "back_pool": [int(b[0]) for b in back_ranked[:bc]], "back_top3": [int(b[0]) for b in back_ranked[:3]], } return self.model["phase5"] # ================================================================ # 阶段6:回测优化(≥50期验证 + 权重自动优化) # ================================================================ def phase6_backtest(self, min_test_periods=51): """阶段6:回测验证 + 权重自动优化(≥50期验证)""" if self.N < min_test_periods + 10: print(f" [⚠️] 数据不足{min_test_periods + 10}期,当前{self.N}期,跳过回测") self.model["phase6"] = {"backtest_passed": False, "reason": "数据不足"} return self.model["phase6"] # 用最近min_test_periods期做回测(减1得≥50期验证) test_start = self.N - min_test_periods test_data = self.history[test_start:] fm = self.cfg["front_max"] fc = self.cfg["front_count"] bc = self.cfg["back_count"] # 生成推荐组合(用回测窗口内的数据重新训练) def generate_combo_from_window(window_data): """从窗口数据生成推荐组合""" all_f = [] for r in window_data: all_f.extend(r["fronts"]) freq = Counter(all_f) scores = {n: freq.get(n, 0) / len(window_data) for n in range(1, fm + 1)} total = sum(scores.values()) scores = {n: s / total for n, s in scores.items()} # 按得分排序取top ranked = sorted(scores.items(), key=lambda x: -x[1]) combo = sorted([r[0] for r in ranked[:fc]]) return combo # 回测验证 hits = 0 total_test = len(test_data) - 1 # 减去第一期(无历史数据可比) for i in range(1, len(test_data)): # 用前i期数据生成推荐 window = test_data[:i] combo = generate_combo_from_window(window) actual = sorted(test_data[i]["fronts"]) # 计算命中数 hit_count = len(set(combo) & set(actual)) if hit_count >= fc // 2: # 命中≥一半算有效 hits += 1 hit_rate = hits / total_test if total_test > 0 else 0 # 权重优化:调整约束参数以提高命中率 constraints = self.cfg["constraints"].copy() # 根据历史数据调整约束 recent = self.history[-30:] odd_dist = Counter() big_dist = Counter() for r in recent: odd = sum(1 for n in r["fronts"] if n % 2 == 1) big = sum(1 for n in r["fronts"] if n > fm / 2) odd_dist[odd] += 1 big_dist[big] += 1 # 取最频繁的奇偶比和大小比 most_common_odd = odd_dist.most_common(1)[0][0] most_common_big = big_dist.most_common(1)[0][0] constraints["odd_even"]["min_odd"] = max(1, most_common_odd - 1) constraints["odd_even"]["max_odd"] = min(fc - 1, most_common_odd + 1) constraints["big_small"]["min_big"] = max(1, most_common_big - 1) constraints["big_small"]["max_big"] = min(fc - 1, most_common_big + 1) self.model["phase6"] = { "backtest_passed": True, "test_periods": total_test, "hit_rate": hit_rate, "hits": hits, "optimized_constraints": constraints, } self.cfg["constraints"] = constraints # 更新约束 return self.model["phase6"] # ================================================================ # 完整运行 # ================================================================ def run_full(self, count=5): """运行完整6阶段""" print(f"\n{'='*60}") print(f" {self.cfg['name']} · 统一6阶段数学建模引擎 v2.0") print(f" 前区{self.cfg['front_count']}个(1~{self.cfg['front_max']}) + 后区{self.cfg['back_count']}个(1~{self.cfg['back_max']})") print(f" 数据量: {self.N}期 | 数据源: {self.cfg['source']}") print(f"{'='*60}") # 阶段1 print(f"\n [阶段1] 数据清洗与基础描述统计") p1 = self.phase1_descriptive_stats() front_top5 = sorted(p1["front_freq"].items(), key=lambda x: -x[1]["freq"])[:5] front_str = " ".join("{0}({1}次)".format(n, v["freq"]) for n, v in front_top5) print(" 前区TOP5热号: " + front_str) back_top3 = sorted(p1["back_freq"].items(), key=lambda x: -x[1]["freq"])[:3] back_str = " ".join("{0}({1}次)".format(n, v["freq"]) for n, v in back_top3) print(" 后区TOP3热号: " + back_str) # 阶段2 print(f"\n [阶段2] 伽马分布拟合") p2 = self.phase2_distribution_fitting() print(f" 前区拟合通过率: {p2['front_pass_rate']*100:.1f}% ({int(p2['front_pass_rate']*self.cfg['front_max'])}/{self.cfg['front_max']})") print(f" 后区拟合通过率: {p2['back_pass_rate']*100:.1f}% ({int(p2['back_pass_rate']*self.cfg['back_max'])}/{self.cfg['back_max']})") # 阶段3 print(f"\n [阶段3] 贝叶斯后验概率") p3 = self.phase3_bayesian_posterior() front_top5 = sorted(p3["front_posterior"].items(), key=lambda x: -float(x[1]))[:5] front_str = " ".join("{0}({1:.4f})".format(n, v) for n, v in front_top5) print(" 前区后验TOP5: " + front_str) back_top3 = sorted(p3["back_posterior"].items(), key=lambda x: -float(x[1]))[:3] back_str = " ".join("{0}({1:.4f})".format(n, v) for n, v in back_top3) print(" 后区后验TOP3: " + back_str) # 阶段4 print(f"\n [阶段4] K-Means聚类降维") p4 = self.phase4_clustering() hot_str = " ".join(str(n) for n in p4['hot'][:8]) warm_str = " ".join(str(n) for n in p4['warm'][:8]) cold_str = " ".join(str(n) for n in p4['cold'][:8]) print(" 热号簇({0}个): {1}...".format(p4['n_hot'], hot_str)) print(" 温号簇({0}个): {1}...".format(p4['n_warm'], warm_str)) print(" 冷号簇({0}个): {1}...".format(p4['n_cold'], cold_str)) # 阶段5 print(f"\n [阶段5] ILP约束优化求解") p5 = self.phase5_ilp_solve(count) print(f" 满足约束的最优组合: {len(p5['front_combos'])}组") for i, (s, c) in enumerate(p5["front_combos"][:3]): combo_str = " ".join("{:02d}".format(n) for n in c) print(" 第{0}组: {1} 得分:{2:.4f}".format(i + 1, combo_str, s)) # 阶段6 print(f"\n [阶段6] 回测验证 + 权重优化") p6 = self.phase6_backtest() if p6["backtest_passed"]: print(f" 回测期数: {p6['test_periods']}期") print(f" 命中率: {p6['hit_rate']*100:.1f}% ({p6['hits']}/{p6['test_periods']})") print(f" 优化后约束: 奇偶{p6['optimized_constraints']['odd_even']}, 大小{p6['optimized_constraints']['big_small']}") else: print(f" 跳过: {p6['reason']}") # 保存模型 model_path = os.path.join(OUTPUT_DIR, self.cfg["model_file"]) json.dump(self.model, open(model_path, "w"), ensure_ascii=False, indent=2) print(f"\n 模型参数已保存: {model_path}") return self.model # ================================================================ # 入口 # ================================================================ if __name__ == "__main__": import numpy as np lt = sys.argv[1] if len(sys.argv) > 1 else "ssq" if lt not in LOTTERY_CONFIGS: print(f"用法: python unified_engine.py [ssq|dlt]") sys.exit(1) engine = UnifiedEngine(lt) engine.run_full()