#!/usr/bin/env python # -*- coding: utf-8 -*- """ 双色球 & 大乐透 号码分析与机选工具 ================================== 功能: 数据采集/分析/推荐/回滚/持久化 数据源: 双色球(500.com) 大乐透(体彩API) """ import json, re, os, hashlib, datetime, subprocess, random, math, sys from collections import Counter # 集成学习模块 try: from ensemble_learning import ensemble_recommendation except ImportError: ensemble_recommendation = None BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DATA_DIR = os.path.join(BASE_DIR, "data") os.makedirs(DATA_DIR, exist_ok=True) LOTTERY_RULES = { "ssq": { "name": "双色球", "source": "500.com", "red_count": 6, "red_max": 33, "blue_count": 1, "blue_max": 16, "draw_days": [2, 4, 7], "draw_time": "21:15", "api_url": "https://datachart.500.com/ssq/history/newinc/history.php?start={start}&end={end}&limit=100", "backup_url": "https://www.17500.cn/api/kaijiang/getlist", "backup_type": "post" }, "dlt": { "name": "大乐透", "source": "中国体育彩票", "red_count": 5, "red_max": 35, "blue_count": 2, "blue_max": 12, "draw_days": [1, 3, 6], "draw_time": "20:25", "api_url": "https://webapi.sporttery.cn/gateway/lottery/getHistoryPageListV1.qry?gameNo=85&provinceId=0&pageSize=100&is498=0&pageNo=1" } } WEEKDAY = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"} class Persistence: def __init__(self): self.config = self.load("config.json", { "version": "1.0.0", "seed": "LOTTERY_SKILL_V1.0.0", "iteration_count": 0, "ssq": {"latest_issue":"","latest_fetch_start":"","latest_fetch_end":""}, "dlt": {"latest_issue":"","latest_fetch_start":"","latest_fetch_end":""} }) def path(self, n): return os.path.join(DATA_DIR, n) def load(self, n, d=None): p=self.path(n) if os.path.exists(p): try: return json.load(open(p,'r',encoding='utf-8')) except: pass return d if d is not None else {} def save(self, n, d): json.dump(d, open(self.path(n),'w',encoding='utf-8'), ensure_ascii=False, indent=2) def history(self, lt): return self.load(f"{lt}_history.json", []) def save_history(self,lt,r): self.save(f"{lt}_history.json", r) def analysis(self,lt): return self.load(f"analysis_{lt}.json", {}) def save_analysis(self,lt,d): self.save(f"analysis_{lt}.json", d) def feedback(self): return self.load("feedback.json", { "iterations":[],"accuracy_history":[], "ssq":{"total":0,"correct_reds":0,"correct_blues":0,"best":0, "profitable_wins":0,"total_earnings":0.0}, "dlt":{"total":0,"correct_reds":0,"correct_blues":0,"best":0, "profitable_wins":0,"total_earnings":0.0} }) def save_feedback(self,d): self.save("feedback.json", d) def save_config(self): self.save("config.json", self.config) def get_seed(self,lt,date=None): if not date: date = datetime.datetime.now().strftime("%Y-%m-%d") # 种子基于:固定种子 + 彩种 + 日期 + 分析版本号 # version 从 analysis_{lt}.json 读取(由 show_analysis 写入), # FeedbackLoop.check() 回滚时不修改该字段,保证同天内结果一致。 # 仅当数据源变化→重新分析→版本号更新时,种子才会变化。 ver = self.analysis(lt).get("version", 0) return hashlib.md5(f"{self.config['seed']}_{lt}_{date}_v{ver}".encode()).hexdigest() class Collector: def __init__(self,p): self.p = p def fetch_ssq(self, count=100): """采集双色球数据,自动容灾切换""" cfg = self.p.config["ssq"] hist = self.p.history("ssq") # 主数据源: 500.com records = self._fetch_ssq_500(hist, cfg, count) # 如果主数据源失败,切换到备用数据源 17500.cn if records is None or len(records) == 0: print("[采集] 500.com 无数据,切换到备用数据源 17500.cn") records = self._fetch_ssq_17500(hist, cfg) return records def _fetch_ssq_500(self, hist, cfg, count=100): """从500.com获取双色球数据(主数据源)- 仅获取新期数据""" # 确定请求范围 if hist: latest = max(int(r["issue"]) for r in hist) # 已采集过的不用再采集,只请求最新一期之后的数据 # 但500.com需要至少一个范围,如果最新期号之后还没开奖, # 缩小请求范围到 latest ~ latest(即只请求最新一期确认) now = datetime.datetime.now() # 估算当前应有的最新期号 est_end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 if latest >= est_end - 3: # 数据已是最新,仅请求最新一期确认是否已有新开奖 start, end = latest, latest + 10 else: # 有较大差距,从最新期+1开始请求 start, end = latest + 1, latest + min(count, est_end - latest + 10) else: # 首次采集,获取100期 now = datetime.datetime.now() end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 start = end - count + 1 if end < start: end = start + count - 1 cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(start), str(end) self.p.save_config() url = LOTTERY_RULES["ssq"]["api_url"].format(start=start, end=end) print("[采集] 500.com 期号%d~%d" % (start, end)) r = subprocess.run(["curl","-s",url,"-H","User-Agent: Mozilla/5.0"], capture_output=True, timeout=30) if r.returncode != 0: print("[警告] 500.com 请求失败") return None html = r.stdout.decode('utf-8', errors='ignore') records = self._parse_ssq_500(html) if not records: print("[警告] 500.com 返回空数据") return None return self._merge_records("ssq", hist, records, cfg) def _fetch_ssq_17500(self, hist, cfg): """从17500.cn获取双色球数据(备用数据源)""" print("[采集] 17500.cn 最近30期") r = subprocess.run( ["curl","-s","-X","POST","-d","lotid=ssq", "https://www.17500.cn/api/kaijiang/getlist", "-H","User-Agent: Mozilla/5.0", "-H","Referer: https://www.17500.cn/kj/list-ssq.html", "-H","Content-Type: application/x-www-form-urlencoded"], capture_output=True, timeout=30) if r.returncode != 0: print("[警告] 17500.cn 请求失败") return None html = r.stdout.decode('utf-8', errors='ignore') records = self._parse_ssq_17500(html) if not records: print("[警告] 17500.cn 解析失败") return None # 更新期数范围 if records: issues = sorted(int(r["issue"]) for r in records) cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(issues[0]), str(issues[-1]) return self._merge_records("ssq", hist, records, cfg) def _merge_records(self, lt, hist, records, cfg): """合并新记录到历史数据""" exist = {r["issue"] for r in hist} new = [r for r in records if r["issue"] not in exist] if new: hist.extend(new) hist.sort(key=lambda x:int(x["issue"])) if len(hist)>500: hist=hist[-500:] self.p.save_history(lt, hist) cfg["latest_issue"] = max(r["issue"] for r in hist) self.p.save_config() print("[采集] %s +%d条 共%d条" % (LOTTERY_RULES[lt]["name"], len(new), len(hist))) return new if new else None def _parse_ssq_500(self, html): """解析500.com的HTML表格""" m = re.search(r']*id="tablelist"[^>]*>(.*?)', html, re.DOTALL) if not m: return [] tb = re.search(r']*>(.*?)', m.group(1), re.DOTALL) body = tb.group(1) if tb else m.group(1) rows = re.findall(r']*>(.*?)', body, re.DOTALL) res = [] for row in rows: row = re.sub(r'','',row) tds = re.findall(r']*>(.*?)', row, re.DOTALL) vals = [re.sub(r'<[^>]+>','',td).strip() for td in tds] if len(vals)<8: continue iss = vals[0].strip() if re.match(r'^\d{5,}$', iss): res.append({ "issue": iss, "reds": [v.zfill(2) for v in vals[1:7]], "blue": vals[7].strip().zfill(2), "date": vals[-1].strip() if len(vals)>=16 else "" }) return res def _parse_ssq_17500(self, html): """解析17500.cn的HTML表格""" tbody = re.search(r'(.*?)', html, re.DOTALL) if not tbody: return [] rows = re.findall(r'(.*?)', tbody.group(1), re.DOTALL) res = [] for row in rows: tds = re.findall(r']*>(.*?)', row, re.DOTALL) vals = [re.sub(r'<[^>]+>', '', td).strip() for td in tds] if len(vals) < 4: continue issue = vals[0].strip() # 17500期号格式为"2026057"(7位),转为"26057"(5位) if len(issue) == 7 and issue.startswith('20'): issue = issue[2:] date = vals[1].strip() if len(vals) > 1 else "" nums_str = re.sub(r'<[^>]+>', '', vals[2]).replace(' ','').strip() if len(vals) > 2 else "" if not issue or not nums_str or nums_str == '-': continue # 17500号码格式: "011022242830 07" -> 红球6个+蓝球(可能1个或多个) parts = nums_str.split() if len(parts) < 2: continue reds_part = parts[0] blues_part = " ".join(parts[1:]) if len(reds_part) < 12: continue # 6个数字*2字符 reds = [reds_part[i:i+2] for i in range(0, 12, 2)] blues = blues_part.replace(' ', '') res.append({ "issue": issue, "reds": reds, "blue": blues, "date": date }) return res def fetch_dlt(self): """采集大乐透数据 - 增量更新,自动容灾切换""" cfg = self.p.config["dlt"] hist = self.p.history("dlt") # 主数据源: 体彩官方API records = self._fetch_dlt_official(hist, cfg) # 如果主数据源失败,切换到备用数据源 500.com if records is None or len(records) == 0: print("[采集] 体彩API无数据,切换到备用数据源 500.com") records = self._fetch_dlt_500(hist, cfg) return records def _fetch_dlt_official(self, hist, cfg): """从体彩官方API获取大乐透数据(主数据源)""" url = LOTTERY_RULES["dlt"]["api_url"] print("[采集] 体彩API") r = subprocess.run(["curl","-s",url], capture_output=True, timeout=30) if r.returncode != 0: return None try: data = json.loads(r.stdout.decode('utf-8', errors='ignore')) except: return None if not data.get("success"): return None records = [] for item in data["value"]["list"]: nums = item["lotteryDrawResult"].split() if len(nums)==7: records.append({ "issue": item["lotteryDrawNum"], "reds": [n.zfill(2) for n in nums[:5]], "blue": [n.zfill(2) for n in nums[5:7]], "date": item["lotteryDrawTime"] }) if records: issues = sorted(int(r["issue"]) for r in records) cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(issues[0]), str(issues[-1]) return self._merge_records("dlt", hist, records, cfg) def _fetch_dlt_500(self, hist, cfg): """从500.com获取大乐透数据(备用数据源)""" # 确定请求范围 if hist: latest = max(int(r["issue"]) for r in hist) now = datetime.datetime.now() est_end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 if latest >= est_end - 3: start, end = latest, latest + 10 else: start, end = latest + 1, latest + min(100, est_end - latest + 10) else: now = datetime.datetime.now() end = int(now.strftime("%y")) * 1000 + int(now.strftime("%j")) // 2 start = end - 99 if end < start: end = start + 99 cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(start), str(end) url = "https://datachart.500.com/dlt/history/newinc/history.php?start={}&end={}".format(start, end) print("[采集] 500.com 期号%d~%d" % (start, end)) r = subprocess.run(["curl","-s",url,"-H","User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"], capture_output=True, timeout=30) if r.returncode != 0: return None html = r.stdout.decode('utf-8', errors='ignore') records = self._parse_dlt_500(html) if not records: return None if records: issues = sorted(int(r["issue"]) for r in records) cfg["latest_fetch_start"], cfg["latest_fetch_end"] = str(issues[0]), str(issues[-1]) return self._merge_records("dlt", hist, records, cfg) def _parse_dlt_500(self, html): """解析500.com大乐透HTML表格""" rows = re.findall(r'(.*?)', html, re.DOTALL) res = [] for row in rows: row = re.sub(r'', '', row) tds = re.findall(r']*>(.*?)', row, re.DOTALL) vals = [re.sub(r'<[^>]+>', '', td).strip() for td in tds] if len(vals) < 8: continue iss = vals[0].strip() if re.match(r'^\d{5,}$', iss): res.append({ "issue": iss, "reds": [v.zfill(2) for v in vals[1:6]], "blue": [v.zfill(2) for v in vals[6:8]], "date": vals[-1].strip() if len(vals) >= 15 else "" }) return res def fetch_all(self): return {"ssq":self.fetch_ssq() is not None, "dlt":self.fetch_dlt() is not None} class Analyzer: """集成学习分析器 - 融合5种模型进行号码分析""" def __init__(self, p, lt): self.p = p self.lt = lt self.rule = LOTTERY_RULES[lt] all_h = p.history(lt) self.data = sorted(all_h, key=lambda x: int(x["issue"]), reverse=True)[:100] self._cached_result = None def analyze(self): if not self.data: return None if self._cached_result is not None: return self._cached_result from ensemble_learning import ensemble_recommendation rule = self.rule lt = self.lt # 运行集成学习(使用完整历史数据,约200期) result = ensemble_recommendation(lt, self.data, rule, count=5) if not result or not result['recommendations']: return None combined_scores = result['combined_scores'] weights = result['weights'] # 构建分析结果,兼容原有接口 r_comp = combined_scores['red'] b_comp = combined_scores['blue'] # 频率分析TOP10 sorted_r = sorted(r_comp.items(), key=lambda x: x[1], reverse=True) top10r = [(i, round(s, 3)) for i, s in sorted_r[:10]] # 蓝球TOP5 sorted_b = sorted(b_comp.items(), key=lambda x: x[1], reverse=True) top5b = [(i, round(s, 3)) for i, s in sorted_b[:5]] # 模式识别 oe_s = Counter() for rec in self.data: ns = [int(x) for x in rec["reds"]] oe_s[f"{sum(1 for n in ns if n%2==1)}:{self.rule['red_count']-sum(1 for n in ns if n%2==1)}"] += 1 most_oe = oe_s.most_common(1)[0][0] if oe_s else "3:2" mid = self.rule["red_max"] / 2 bs_s = Counter() for rec in self.data: ns = [int(x) for x in rec["reds"]] bs_s[f"{sum(1 for n in ns if n>mid)}:{self.rule['red_count']-sum(1 for n in ns if n>mid)}"] += 1 most_bs = bs_s.most_common(1)[0][0] if bs_s else "3:2" sums = [sum(int(x) for x in r["reds"]) for r in self.data] avg_s = sum(sums) / len(sums) if sums else 0 std_s = math.sqrt(sum((s - avg_s) ** 2 for s in sums) / len(sums)) if sums else 0 result_data = { "version": 7, "source": rule["source"], "data_range": { "start": self.data[-1]["issue"], "end": self.data[0]["issue"], "total": len(self.data) }, "red_composite": r_comp, "blue_composite": b_comp, "patterns": { "most_oe": most_oe, "most_bs": most_bs, "sum_range": f"[{round(avg_s - std_s, 1)}, {round(avg_s + std_s, 1)}]" }, "top10_reds": top10r, "top5_blues": top5b, "model_weights": weights, "ensemble_type": "5-model weighted integration" } self._cached_result = result_data return result_data class Recommender: """集成学习推荐器 - 基于多模型融合生成推荐""" def __init__(self, p, analyzer, lt): self.p = p self.analyzer = analyzer self.lt = lt self.rule = LOTTERY_RULES[lt] self.analysis = analyzer.analyze() def recommend(self, count=5): if not self.analysis: return [] rule = self.rule rmax = rule["red_max"] bmax = rule["blue_max"] rcnt = rule["red_count"] bcnt = rule["blue_count"] r_comp = self.analysis["red_composite"] b_comp = self.analysis["blue_composite"] # 使用集成学习直接获取推荐 from ensemble_learning import EnsembleLearner all_h = self.p.history(self.lt) sorted_h = sorted(all_h, key=lambda x: int(x["issue"]), reverse=True) learner = EnsembleLearner(self.lt, sorted_h, rule) recommendations = learner.get_recommendations(count=count) return recommendations class FeedbackLoop: def __init__(self,p): self.p=p def check(self, lt, earnings=0.0): fb = self.p.feedback() hist = self.p.history(lt) if not hist: return None latest = max(hist, key=lambda x:int(x["issue"])) actual_r = [int(x) for x in latest["reds"]] actual_b = [int(x) for x in (latest["blue"] if isinstance(latest["blue"],list) else [latest["blue"]])] analysis = self.p.analysis(lt) last_rec = analysis.get("last_recommendation") if not last_rec: return None pred_r, pred_b = last_rec["reds"], last_rec["blues"] hit_r = [n for n in pred_r if n in actual_r] hit_b = [n for n in pred_b if n in actual_b] stats = fb[lt] stats["total"]+=1; stats["correct_reds"]+=len(hit_r); stats["correct_blues"]+=len(hit_b) stats["best"] = max(stats["best"], len(hit_r)+len(hit_b)) # 记录收益(>0 表示产生了实际盈利) if earnings > 0: stats["profitable_wins"] = stats.get("profitable_wins", 0) + 1 stats["total_earnings"] = round(stats.get("total_earnings", 0.0) + earnings, 2) it = { "lt":lt,"issue":latest["issue"],"date":latest.get("date",""), "pred_reds":pred_r,"pred_blues":pred_b, "act_reds":actual_r,"act_blues":actual_b, "hit_reds":hit_r,"hit_blues":hit_b, "red_hits":len(hit_r),"blue_hits":len(hit_b),"total_hits":len(hit_r)+len(hit_b), "earnings":earnings } fb["iterations"].append(it) total_pred = sum(i["red_hits"]+i["blue_hits"] for i in fb["iterations"] if i["lt"]==lt) total_possible = stats["total"]*(LOTTERY_RULES[lt]["red_count"]+LOTTERY_RULES[lt]["blue_count"]) avg_acc = round(total_pred/total_possible*100, 2) if total_possible else 0 fb["accuracy_history"].append({"issue":latest["issue"],"lt":lt,"hits":len(hit_r)+len(hit_b),"avg_accuracy":avg_acc}) self.p.save_feedback(fb) self.p.config["iteration_count"] += 1 self.p.save_config() name = LOTTERY_RULES[lt]["name"] print("\n" + "="*60) print("[回滚] %s 第%s期 开奖校验" % (name, latest["issue"])) print(" 推荐红球: %s" % pred_r) print(" 实际红球: %s" % actual_r) print(" 命中红球: %s (%d个)" % (hit_r, len(hit_r))) print(" 推荐蓝球: %s" % pred_b) print(" 实际蓝球: %s" % actual_b) print(" 命中蓝球: %s (%d个)" % (hit_b, len(hit_b))) print(" 总命中: %d/%d" % (len(hit_r)+len(hit_b), LOTTERY_RULES[lt]["red_count"]+LOTTERY_RULES[lt]["blue_count"])) print(" 累计平均精度: %.2f%%" % avg_acc) print(" 迭代版本: v%d" % self.p.config["iteration_count"]) print("="*60 + "\n") return it # ==================== 全量回滚迭代 ==================== class FullBacktest: """全量回溯测试:从近100期的第1期开始逐期模拟分析推理→机选→回滚""" def __init__(self, p): self.p = p def backtest(self, lt, target_count=100): """ 全量回滚迭代测试 :param lt: 彩种 ssq/dlt :param target_count: 目标回滚期数(默认100) :return: 汇总统计 dict """ rule = LOTTERY_RULES[lt] name = rule["name"] rmax = rule["red_max"] # 获取全部历史数据(按升序) all_hist = self.p.history(lt) if not all_hist: print("[%s] 无历史数据" % name) return None total_available = len(all_hist) # 如果可用数据不足100期,使用全部数据 actual_count = min(target_count, total_available) print("\n[%s] 全量回滚迭代测试 | 总数据: %d期 | 目标: %d期 | 起始: 第%s期 ~ 结束: 第%s期" % ( name, total_available, actual_count, all_hist[-(actual_count)]["issue"], all_hist[-1]["issue"])) print("=" * 80) # 逐期向前模拟(从最近的目标期往前推) start_idx = total_available - actual_count results = [] for phase in range(actual_count): # 目标期索引(从最近往前推) target_idx = start_idx + phase target_issue_data = all_hist[target_idx] target_issue = int(target_issue_data["issue"]) # 训练数据:target_idx 之前所有的已知数据(不包含当期及之后) train_data = all_hist[:target_idx] if len(train_data) < 10: continue # 数据太少跳过 # 创建临时 Analyzer,注入训练数据 class TempAnalyzer: def __init__(self, data, lt, rule, p): self.data = sorted(data, key=lambda x:int(x["issue"]), reverse=True)[:100] self.lt = lt self.rule = rule self.p = p self._cached_result = None def analyze(self): if self._cached_result is None and self.data: # 复用原 Analyzer 的分析逻辑(简化版) self._cached_result = _run_analysis(self.data, self.lt, self.rule, self.p) return self._cached_result temp_analyzer = TempAnalyzer(train_data, lt, rule, self.p) analysis = temp_analyzer.analyze() if not analysis: continue # 用推荐器生成推荐 pred_r, pred_b = _generate_prediction_from_analysis(analysis, lt, rule, self.p, ci=0) # 实际开奖号码 act_r = [int(x) for x in target_issue_data["reds"]] act_b = target_issue_data["blue"] if isinstance(target_issue_data["blue"], list) else [int(target_issue_data["blue"])] # 计算命中 hit_r = [n for n in pred_r if n in act_r] hit_b = [n for n in pred_b if n in act_b] red_hits = len(hit_r) blue_hits = len(hit_b) total_hits = red_hits + blue_hits results.append({ "issue": target_issue, "pred_reds": pred_r, "pred_blues": pred_b, "act_reds": act_r, "act_blues": act_b, "hit_reds": hit_r, "hit_blues": hit_b, "red_hits": red_hits, "blue_hits": blue_hits, "total_hits": total_hits, "phase": phase + 1 }) # 汇总统计 total_simulations = len(results) total_red_hits = sum(r["red_hits"] for r in results) total_blue_hits = sum(r["blue_hits"] for r in results) total_possible = total_simulations * (rule["red_count"] + rule["blue_count"]) # 命中率分档统计 perfect_hits = sum(1 for r in results if r["red_hits"] >= rule["red_count"] and r["blue_hits"] >= rule["blue_count"]) good_hits = sum(1 for r in results if r["total_hits"] >= 3) some_hits = sum(1 for r in results if 1 <= r["total_hits"] < 3) zero_hits = sum(1 for r in results if r["total_hits"] == 0) avg_red_accuracy = round(total_red_hits / (total_simulations * rule["red_count"]) * 100, 2) if total_simulations else 0 avg_blue_accuracy = round(total_blue_hits / (total_simulations * rule["blue_count"]) * 100, 2) if total_simulations else 0 avg_overall_accuracy = round((total_red_hits + total_blue_hits) / total_possible * 100, 2) if total_possible else 0 avg_red_per_issue = round(total_red_hits / total_simulations, 2) if total_simulations else 0 avg_blue_per_issue = round(total_blue_hits / total_simulations, 2) if total_simulations else 0 max_red_hits = max((r["red_hits"] for r in results), default=0) max_total_hits = max((r["total_hits"] for r in results), default=0) # 红球命中率趋势(前33期、中段33期、后33期) chunks = {} chunk_size = max(1, len(results) // 3) if len(results) >= chunk_size * 3: chunks["前33%"] = { "simulations": len(results[:chunk_size]), "avg_red_hits": round(sum(r["red_hits"] for r in results[:chunk_size])/len(results[:chunk_size]), 2) if results[:chunk_size] else 0, "avg_blue_hits": round(sum(r["blue_hits"] for r in results[:chunk_size])/len(results[:chunk_size]), 2) if results[:chunk_size] else 0, } chunks["中段33%"] = { "simulations": len(results[chunk_size:chunk_size*2]), "avg_red_hits": round(sum(r["red_hits"] for r in results[chunk_size:chunk_size*2])/len(results[chunk_size:chunk_size*2]), 2) if results[chunk_size:chunk_size*2] else 0, "avg_blue_hits": round(sum(r["blue_hits"] for r in results[chunk_size:chunk_size*2])/len(results[chunk_size:chunk_size*2]), 2) if results[chunk_size:chunk_size*2] else 0, } chunks["后33%"] = { "simulations": len(results[chunk_size*2:]), "avg_red_hits": round(sum(r["red_hits"] for r in results[chunk_size*2:])/len(results[chunk_size*2:]), 2) if results[chunk_size*2:] else 0, "avg_blue_hits": round(sum(r["blue_hits"] for r in results[chunk_size*2:])/len(results[chunk_size*2:]), 2) if results[chunk_size*2:] else 0, } elif len(results) > 0: chunks["全部"] = { "simulations": len(results), "avg_red_hits": round(sum(r["red_hits"] for r in results)/len(results), 2) if results else 0, "avg_blue_hits": round(sum(r["blue_hits"] for r in results)/len(results), 2) if results else 0, } # 保存汇总结果到 feedback.json fb = self.p.feedback() fb["backtest_%s" % lt] = { "total_simulations": total_simulations, "total_red_hits": total_red_hits, "total_blue_hits": total_blue_hits, "avg_red_accuracy": avg_red_accuracy, "avg_blue_accuracy": avg_blue_accuracy, "avg_overall_accuracy": avg_overall_accuracy, "avg_red_per_issue": avg_red_per_issue, "avg_blue_per_issue": avg_blue_per_issue, "max_red_hits": max_red_hits, "max_total_hits": max_total_hits, "perfect_hits": perfect_hits, "good_hits": good_hits, "some_hits": some_hits, "zero_hits": zero_hits, "trend": chunks } self.p.save_feedback(fb) # =================== 输出汇总报告 =================== print("\n" + "=" * 80) print("[3/5] 汇总统计") print("=" * 80) print(" 有效模拟: %d期" % total_simulations) print("") print(" === 命中率 ===") print(" 累计红球命中: %d/%d (%.2f%%)" % (total_red_hits, total_simulations*rule["red_count"], avg_red_accuracy)) print(" 累计蓝球命中: %d/%d (%.2f%%)" % (total_blue_hits, total_simulations*rule["blue_count"], avg_blue_accuracy)) print(" 累计总命中: %d/%d (%.2f%%)" % (total_red_hits+total_blue_hits, total_possible, avg_overall_accuracy)) print("") print(" === 每期平均 ===") print(" 每期平均命中红球: %.2f个" % avg_red_per_issue) print(" 每期平均命中蓝球: %.2f个" % avg_blue_per_issue) print(" 最大红球命中: %d个" % max_red_hits) print(" 最大总命中: %d个" % max_total_hits) print("") print(" === 表现分布 ===") print(" [WIN] 完美命中: %d期 (%.1f%%)" % (perfect_hits, perfect_hits/total_simulations*100)) print(" [GOOD] 优秀(≥3命中): %d期 (%.1f%%)" % (good_hits, good_hits/total_simulations*100)) print(" [OK] 一般(1~2命中): %d期 (%.1f%%)" % (some_hits, some_hits/total_simulations*100)) print(" [BAD] 未命中: %d期 (%.1f%%)" % (zero_hits, zero_hits/total_simulations*100)) print("") if chunks: print(" === 趋势分析 ===") for label, stats in chunks.items(): avg_r = stats["avg_red_hits"] avg_b = stats["avg_blue_hits"] sim_n = stats["simulations"] acc_r = round(avg_r/rule["red_count"]*100, 1) if rule["red_count"] else 0 acc_b = round(avg_b/rule["blue_count"]*100, 1) if rule["blue_count"] else 0 print(" %-8s: 红%.2f个(%.1f%%) | 蓝%.2f个(%.1f%%) | %d期" % ( label, avg_r, acc_r, avg_b, acc_b, sim_n)) print("") # 判断趋势是否改善 if len(chunks) >= 2: keys = list(chunks.keys()) first_chunk = chunks[keys[0]] last_chunk = chunks[keys[-1]] first_acc = (first_chunk["avg_red_hits"]/rule["red_count"]*100 + first_chunk["avg_blue_hits"]/rule["blue_count"]*100) / 2 last_acc = (last_chunk["avg_red_hits"]/rule["red_count"]*100 + last_chunk["avg_blue_hits"]/rule["blue_count"]*100) / 2 diff = last_acc - first_acc direction = "↑ 提升" if diff > 0 else ("↓ 下降" if diff < 0 else "→ 持平") print(" 趋势: %.1f%% → %.1f%% → %s (%.1f%%)" % (first_acc, last_acc, direction, abs(diff))) print("\n" + "=" * 80) print("[4/5] 示例样本(最近5期模拟详情)") print("=" * 80) display_samples = results[-5:] if len(results) >= 5 else results for s in display_samples: issue = s["issue"] pred = ["%02d"%n for n in s["pred_reds"]] act = ["%02d"%n for n in s["act_reds"]] pred_b_str = " ".join("%02d"%n for n in s["pred_blues"]) act_b_str = " ".join("%02d"%int(n) for n in s["act_blues"]) hr = ["%02d"%n for n in s["hit_reds"]] hb = ["%02d"%n for n in s["hit_blues"]] marker = "[GOOD]" if s["total_hits"] >= 3 else ("[OK]" if s["total_hits"] >= 1 else "[BAD]") print(" %s 期%d | 推荐: %s +%s | 实际: %s +%s | 命中: 红%s(%d) 蓝%s(%d) | %s" % ( name, issue, " ".join(pred), pred_b_str, " ".join(act), act_b_str, " ".join(hr), s["red_hits"], " ".join(hb), s["blue_hits"], marker)) print("") print("=" * 80) print("[5/5] 诊断结论 & 优化建议") print("=" * 80) issues = [] suggestions = [] # 诊断规则 if avg_overall_accuracy < 10: issues.append("总体命中率偏低(%.2f%%),低于10%基线" % avg_overall_accuracy) suggestions.append("- 检查特征工程:是否存在过拟合/欠拟合问题?考虑调整维度权重分配") elif avg_overall_accuracy < 15: issues.append("总体命中率一般(%.2f%%),接近随机水平" % avg_overall_accuracy) suggestions.append("- 可尝试增加短期动量特征(如连续出现趋势)作为补充维度") if avg_red_accuracy < avg_red_per_issue / rule["red_count"] * 100 * 0.7: issues.append("红球命中率不稳定,波动较大") suggestions.append("- 加强区间平衡和奇偶比约束,减少极端分布") if zero_hits / total_simulations > 0.5: issues.append("超过50%%的期数完全没有命中") suggestions.append("- 推荐算法可能过于集中(过度依赖TOP号码),考虑扩大候选池") # 趋势判断 if len(chunks) >= 2: first_keys = list(chunks.keys())[:1] last_keys = list(chunks.keys())[-1:] fc = chunks[first_keys[0]] lc = chunks[last_keys[0]] first_total = fc["avg_red_hits"] + fc["avg_blue_hits"] last_total = lc["avg_red_hits"] + lc["avg_blue_hits"] if last_total <= first_total: issues.append("随模拟推进(数据窗口增大),命中率未见提升") suggestions.append("- 说明当前分析框架需要结构性优化,而非简单调参") if not issues: print(" [OK] 各项指标均在合理范围内,分析框架有效运行") else: print(" [WARN] 以下问题待解决:") for i, issue in enumerate(issues, 1): print(" %d. %s" % (i, issue)) print("") print(" [SUGGEST] 优化建议:") for s in suggestions: print(" - %s" % s) print("\n [DATA] 详细数据已保存至: feedback.json → backtest_{lt}") print("=" * 80 + "\n") return { "total_simulations": total_simulations, "avg_red_accuracy": avg_red_accuracy, "avg_blue_accuracy": avg_blue_accuracy, "avg_overall_accuracy": avg_overall_accuracy, "issues": issues, "suggestions": suggestions } def _run_analysis(data, lt, rule, p): """执行分析(与 Analyzer.analyze() 逻辑一致,但支持自定义数据)""" rmax, bmax, rcnt = rule["red_max"], rule["blue_max"], rule["red_count"] all_r, all_b = [], [] for rec in data: all_r.extend(int(x) for x in rec["reds"]) bl = rec["blue"] all_b.extend(int(x) for x in (bl if isinstance(bl,list) else [bl])) rf, bf = Counter(all_r), Counter(all_b) max_rf, max_bf = max(rf.values()) or 1, max(bf.values()) or 1 rf_score = {i: rf.get(i,0)/max_rf for i in range(1,rmax+1)} bf_score = {i: bf.get(i,0)/max_bf for i in range(1,bmax+1)} hot = data[:20] hot_r = [int(x) for r in hot for x in r["reds"]] hot_rf = Counter(hot_r); max_hr = max(hot_rf.values()) or 1 hot_score = {i: hot_rf.get(i,0)/max_hr for i in range(1,rmax+1)} r_miss = {} for n in range(1,rmax+1): cnt=0 for r in data: if n in [int(x) for x in r["reds"]]: break cnt+=1 r_miss[n]=cnt b_miss = {} for n in range(1,bmax+1): cnt=0 for r in data: ns = [int(x) for x in (r["blue"] if isinstance(r["blue"],list) else [r["blue"]])] if n in ns: break cnt+=1 b_miss[n]=cnt max_rm, max_bm = max(r_miss.values()) or 1, max(b_miss.values()) or 1 rm_score = {n: math.sin(m/max_rm*math.pi)*0.8+0.2 for n,m in r_miss.items()} bm_score = {n: math.sin(m/max_bm*math.pi)*0.8+0.2 for n,m in b_miss.items()} oe_s = Counter() for rec in data: ns = [int(x) for x in rec["reds"]] oe_s["%d:%d" % (sum(1 for n in ns if n%2==1), rcnt-sum(1 for n in ns if n%2==1))]+=1 most_oe = oe_s.most_common(1)[0][0] if oe_s else "3:3" mid = rmax/2 bs_s = Counter() for rec in data: ns = [int(x) for x in rec["reds"]] bs_s["%d:%d" % (sum(1 for n in ns if n>mid), rcnt-sum(1 for n in ns if n>mid))]+=1 most_bs = bs_s.most_common(1)[0][0] if bs_s else "3:3" sums_list = [sum(int(x) for x in r["reds"]) for r in data] avg_s = sum(sums_list)/len(sums_list) if sums_list else 0 std_s = math.sqrt(sum((s-avg_s)**2 for s in sums_list)/len(sums_list)) if sums_list else 0 tail_f = Counter() for rec in data: for ns in rec["reds"]: val = int(ns) if not isinstance(ns, int) else ns tail_f[val] += 1 max_tf = max(tail_f.values()) or 1 tail_sc = {t: tail_f.get(t,0)/max_tf for t in range(10)} zones = [(1,11),(12,22),(23,33)] if rmax==33 else [(1,12),(13,24),(25,35)] zc = [0]*len(zones) for rec in data: for n in [int(x) for x in rec["reds"]]: for i,(zs,ze) in enumerate(zones): if zs<=n<=ze: zc[i]+=1; break avg_zone = len(data)*rcnt/len(zones) zb = [c/avg_zone if avg_zone else 1.0 for c in zc] r_comp = {} for n in range(1,rmax+1): s = rf_score.get(n,0)*0.25 + hot_score.get(n,0)*0.20 + rm_score.get(n,0.5)*0.20 + tail_sc.get(n%10,0.5)*0.15 for i,(zs,ze) in enumerate(zones): if zs<=n<=ze and zb[i]<0.85: s+=0.10; break r_comp[n] = round(s,4) recent_b = [] for r in data[:20]: recent_b.extend(int(x) for x in (r["blue"] if isinstance(r["blue"],list) else [r["blue"]])) rbf = Counter(recent_b); max_rbf = max(rbf.values()) or 1 b_comp = {} for n in range(1,bmax+1): s = bf_score.get(n,0)*0.35 + bm_score.get(n,0.5)*0.40 + (rbf.get(n,0)/max_rbf)*0.25 b_comp[n] = round(s,4) return { "version": 999, "lottery_type": lt, "lottery_name": rule["name"], "data_range": {"total":len(data)}, "red_composite": r_comp, "blue_composite": b_comp, "patterns": {"most_oe": most_oe, "most_bs": most_bs, "sum_range": [round(avg_s-std_s,1), round(avg_s+std_s,1)]} } def _generate_prediction_from_analysis(analysis, lt, rule, p, ci=0): """根据已有分析结果提取最佳预测号码(返回红球和蓝球两个列表)""" r_comp = analysis.get("red_composite", {}) b_comp = analysis.get("blue_composite", {}) rmax = rule["red_max"] bmax = rule["blue_max"] sorted_r = sorted(r_comp.items(), key=lambda x:x[1], reverse=True) top_r = [n for n,s in sorted_r[:rmax]] top_b = sorted(b_comp.items(), key=lambda x:x[1], reverse=True)[:rule["blue_count"]] pred_b = [n for n,s in top_b] pred_b.sort() pred_r = [] seed = hashlib.md5(("%s_bt_%s" % (p.config.get("seed",""), str(ci))).encode()).hexdigest() cr = random.Random(seed) cand = list(top_r) w = [r_comp.get(n,0.5) for n in cand] while len(pred_r) < rule["red_count"] and cand: tw = sum(w) if tw<=0: pk = cr.choice(cand) else: rv = cr.random()*tw; cum=0; pk=cand[-1] for i,ww in enumerate(w): cum+=ww if rv<=cum: pk=cand[i]; break if pk not in pred_r: pred_r.append(pk) idx = cand.index(pk); cand.pop(idx); w.pop(idx) pred_r.sort() return pred_r, pred_b # ==================== 展示函数 ==================== def print_header(): print("\n" + "*"*60) print(" 双色球 & 大乐透 * 智能号码分析系统 v1.0") print(" 基于多维度数学模型 + 概率分析的机选推荐") print("*"*60 + "\n") def print_today_info(): now = datetime.datetime.now() wd = now.isoweekday() print("[%s] %s" % (now.strftime("%Y-%m-%d %H:%M"), WEEKDAY.get(wd,''))) ssq_today = wd in LOTTERY_RULES["ssq"]["draw_days"] dlt_today = wd in LOTTERY_RULES["dlt"]["draw_days"] print(" 双色球: %s (二/四/日)" % ("[今晚开奖!]" if ssq_today else "[今日不开奖]")) print(" 大乐透: %s (一/三/六)" % ("[今晚开奖!]" if dlt_today else "[今日不开奖]")) print() return ssq_today, dlt_today def show_analysis(p, lt): analyzer = Analyzer(p, lt) result = analyzer.analyze() if not result: print("\n[%s] 暂无数据" % LOTTERY_RULES[lt]["name"]) return None rule = LOTTERY_RULES[lt] print("\n" + "-"*50) print("[%s] 分析报告" % rule["name"]) print("-"*50) print(" 数据范围: 近%d期" % result["data_range"]["total"]) print(" TOP红球: ", end="") r_comp = result["red_composite"] for n,s in sorted(r_comp.items(), key=lambda x:x[1], reverse=True)[:10]: print("%02d(%.3f)" % (n, s), end=" ") print() print(" TOP蓝球: ", end="") b_comp = result["blue_composite"] for n,s in sorted(b_comp.items(), key=lambda x:x[1], reverse=True)[:5]: print("%02d(%.3f)" % (n, s), end=" ") print() pat = result["patterns"] print(" 模式: 奇偶%s 大小%s 和值范围%s" % (pat["most_oe"], pat["most_bs"], pat["sum_range"])) print(" 分析版本: v%d | 数据源: %s" % (result["version"], rule["source"])) print(" 数据范围: 第%s期 ~ 第%s期" % (result["data_range"]["start"], result["data_range"]["end"])) return result def show_recommendation(p, lt, count=5): analyzer = Analyzer(p, lt) rec = Recommender(p, analyzer, lt) results = rec.recommend(count) if not results: print("\n[%s] 无法生成推荐" % LOTTERY_RULES[lt]["name"]) return rule = LOTTERY_RULES[lt] print("\n" + "="*60) print("[%s] 机选推荐 (%s)" % (rule["name"], datetime.datetime.now().strftime("%m/%d %H:%M"))) print("="*60) for i, r in enumerate(results): r_str = " ".join("%02d" % n for n in r["reds"]) b_str = " ".join("%02d" % n for n in r["blues"]) print("\n 第%d组: [红] %s + [蓝] %s" % (i+1, r_str, b_str)) print(" 得分:%.4f | 奇偶:%s | 大小:%s | 和:%d" % (r["score"], r["odd_even"], r["big_small"], r["sum"])) # 保存最佳推荐供回滚 if results: analysis = p.analysis(lt) analysis["last_recommendation"] = { "reds":results[0]["reds"], "blues":results[0]["blues"], "generated_at":datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } p.save_analysis(lt, analysis) return results def show_ensemble_recommendation(p, lt, count=5): """集成学习推荐:多模型融合 + 权重优化""" if ensemble_recommendation is None: print("\n[错误] 集成学习模块未加载") return rule = LOTTERY_RULES[lt] name = rule["name"] rmax = rule["red_max"] rcnt = rule["red_count"] bmax = rule["blue_max"] bcnt = rule["blue_count"] print("\n" + "="*70) print(f"🧠 [{name}] 集成学习推荐 (多模型融合 + 权重优化)") print(f"{'='*70}") # 获取历史数据 hist = p.history(lt) if not hist: print(f"\n[{name}] 暂无历史数据") return # 使用最近100期数据作为训练集 sorted_hist = sorted(hist, key=lambda x: int(x["issue"]), reverse=True)[:100] # 运行集成学习 result = ensemble_recommendation(lt, sorted_hist, rule, count=count) if not result or not result['recommendations']: print("\n[错误] 集成学习推荐失败") return weights = result['weights'] backtest = result['backtest'] recommendations = result['recommendations'] # 显示各模型权重 print("\n" + "-"*50) print("📊 各模型权重(回测优化后)") print("-"*50) model_names = { 'freq': '频率加权模型', 'bayes': '贝叶斯平滑模型', 'markov': '马尔可夫链模型', 'kmeans': 'K-Means聚类模型', 'residual': '残差回归模型' } for m, w in sorted(weights.items(), key=lambda x: -x[1]): print(f" {model_names.get(m, m):<20s}: {w:.4f}") print(f"\n 回测迭代: {backtest['iterations']} 次 | 测试期数: {backtest['test_periods']}") print(f" 回测总分: {backtest['best_score']:.1f} (分数越高表示匹配度越好)") # 显示推荐号码 print("\n" + "-"*50) print("🎯 集成学习推荐号码") print("-"*50) today = datetime.datetime.now() draw_days = rule["draw_days"] draw_time = rule.get("draw_time", "20:00") draw_hour, draw_min = map(int, draw_time.split(":")) now_hour, now_min = today.hour, today.minute today_draw = today.isoweekday() in draw_days time_past = (now_hour > draw_hour) or (now_hour == draw_hour and now_min >= draw_min) if today_draw and not time_past: delta = 0 else: delta = 1 while (today + datetime.timedelta(days=delta)).isoweekday() not in draw_days: delta += 1 next_draw_date = (today + datetime.timedelta(days=delta)).strftime("%Y-%m-%d") wd_map = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"} next_wd = wd_map.get((today + datetime.timedelta(days=delta)).isoweekday(), "") print(f"\n 即将开奖: 第{int(sorted_hist[0]['issue'])+1}期 | {next_draw_date} ({next_wd})") print() for i, r in enumerate(recommendations): r_str = " ".join("%02d" % n for n in r["reds"]) b_str = " ".join("%02d" % n for n in r["blues"]) star = " ⭐" if i == 0 else "" print(f" 第{i+1}组: [前区] {r_str} + [后区] {b_str}") print(f" 综合得分: {r['score']:.4f}{star} | 奇偶比: {r['odd_even']} | 大小比: {r['big_small']} | 和值: {r['sum']}") # 保存最佳推荐供回滚 if recommendations: analysis = p.analysis(lt) analysis["last_ensemble_recommendation"] = { "reds": recommendations[0]["reds"], "blues": recommendations[0]["blues"], "generated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "weights": weights, "backtest": backtest } p.save_analysis(lt, analysis) print("\n" + "="*70) print(" 说明:集成学习融合5种算法,通过历史数据回测自动优化权重") print(" 分数越高表示该号码组在历史模式中匹配度越高") print("="*70) return recommendations def show_full_report(p, lt, count=5): """4块结构化输出:回滚迭代 + 数据采集 + 推理分析 + 机选号码""" rule = LOTTERY_RULES[lt] fb = p.feedback() stats = fb.get(lt, {}) analysis = p.analysis(lt) hist = p.history(lt) if not hist: print("\n[%s] 暂无数据" % rule["name"]) return # 获取历史最新期号 latest = max(hist, key=lambda x:int(x["issue"])) prev_issue = latest["issue"] prev_date = latest.get("date","") prev_reds = latest["reds"] prev_blues = latest["blue"] if isinstance(latest["blue"],list) else [latest["blue"]] next_issue = str(int(prev_issue)+1) # 计算下期开奖日期 today = datetime.datetime.now() draw_days = rule["draw_days"] # 如果今天就是开奖日且还没过开奖时间,则下期就是今天 draw_time = rule.get("draw_time", "20:00") draw_hour, draw_min = map(int, draw_time.split(":")) now_hour, now_min = today.hour, today.minute today_draw = today.isoweekday() in draw_days time_past = (now_hour > draw_hour) or (now_hour == draw_hour and now_min >= draw_min) if today_draw and not time_past: delta = 0 else: delta = 1 while (today + datetime.timedelta(days=delta)).isoweekday() not in draw_days: delta += 1 next_draw_date = (today + datetime.timedelta(days=delta)).strftime("%Y-%m-%d") wd_map = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"} next_wd = wd_map.get((today + datetime.timedelta(days=delta)).isoweekday(), "") next_draw_date += " (%s)" % next_wd # 回滚统计 ver = p.config.get("iteration_count", 0) total_recs = stats.get("total", 0) profitable = stats.get("profitable_wins", 0) win_rate = "%.1f%%" % (profitable/total_recs*100) if total_recs > 0 else "0.0%" earnings = stats.get("total_earnings", 0.0) # 分析 analyzer = Analyzer(p, lt) result = analyzer.analyze() if not result: print("\n[%s] 无法分析" % rule["name"]) return # ==================== 第1块:回滚迭代 ==================== print("\n" + "="*60) print(" [1/4] 回滚迭代") print("="*60) print(" 当前版本号: v%d" % ver) print(" 版本胜率: %s (%d期推荐 / %d次盈利)" % (win_rate, total_recs, profitable)) print(" 累计中奖收益金额: RMB %.2f" % earnings) # ==================== 第2块:数据采集 ==================== dr = result["data_range"] print("\n" + "="*60) print(" [2/4] 数据采集") print("="*60) print(" 采集期数范围: 第%s期 ~ 第%s期(近%d期)" % (dr["start"], dr["end"], dr["total"])) print(" 上一期号: 第%s期" % prev_issue) print(" 开奖日期: %s" % prev_date) r_str = " ".join("%02d" % int(x) for x in prev_reds) b_str = " ".join("%02d" % int(x) for x in prev_blues) if lt == "ssq": print(" 开奖号码: 红球 %s 蓝球 %s" % (r_str, b_str)) else: print(" 开奖号码: 前区 %s 后区 %s" % (r_str, b_str)) # ==================== 第3块:推理分析 ==================== pat = result["patterns"] print("\n" + "="*60) print(" [3/4] 推理分析(分析版本 v%d | 数据源: %s)" % (result["version"], rule["source"])) print("="*60) # 步骤1:集成学习评分(TOP10前区) r_comp = result["red_composite"] top10r = sorted(r_comp.items(), key=lambda x:x[1], reverse=True)[:10] print(" >> 步骤1 - 集成学习评分(前区)") print(" %s" % " ".join("%02d(%.4f)"%(n,s) for n,s in top10r)) # 步骤2:集成学习评分(TOP10后区) b_comp = result["blue_composite"] top5b = sorted(b_comp.items(), key=lambda x:x[1], reverse=True)[:5] print(" >> 步骤2 - 集成学习评分(后区)") print(" %s" % " ".join("%02d(%.4f)"%(n,s) for n,s in top5b)) # 步骤3:模式识别 print(" >> 步骤3 - 模式识别") print(" 奇偶比: %s | 大小比: %s | 和值区间: [%s, %s]" % ( pat["most_oe"], pat["most_bs"], pat["sum_range"][0], pat["sum_range"][1])) # 步骤4:权重体系(集成学习模型) print(" >> 步骤4 - 权重体系(集成学习模型)") weights = result.get("model_weights", {}) if weights: name_map = {'freq':'频率加权', 'bayes':'贝叶斯平滑', 'markov':'马尔可夫链', 'kmeans':'K-Means聚类', 'residual':'残差回归'} weight_lines = [] for m, w in weights.items(): weight_lines.append("%s%.4f%%" % (name_map.get(m, m), w * 100)) print(" %s" % " + ".join(weight_lines)) else: print(" 频率25% + 热号20% + 遗漏15% + 尾数10% + 区间8%") print(" 奇偶8% + 大小6% + 和值4% + 概率2% + 连号2%") # ==================== 第4块:机选号码 ==================== rec = Recommender(p, analyzer, lt) results = rec.recommend(count) if not results: print("\n[%s] 无法生成推荐" % rule["name"]) return print("\n" + "="*60) print(" [4/4] 机选号码(第%s期 | %s)" % (next_issue, next_draw_date)) print("="*60) for i, r in enumerate(results): if lt == "ssq": r_str = " ".join("%02d" % n for n in r["reds"]) b_str = "%02d" % r["blues"][0] print("\n 第%d组: 红球 %s 蓝球 %s" % (i+1, r_str, b_str)) else: r_str = " ".join("%02d" % n for n in r["reds"]) b_str = " ".join("%02d" % n for n in r["blues"]) print("\n 第%d组: 前区 %s + 后区 %s" % (i+1, r_str, b_str)) print(" 得分:%.4f | 奇偶:%s | 大小:%s | 和:%d" % (r["score"], r["odd_even"], r["big_small"], r["sum"])) # 保存推荐 if results: analysis["last_recommendation"] = { "reds":results[0]["reds"], "blues":results[0]["blues"], "generated_at":datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") } p.save_analysis(lt, analysis) print("\n" + "="*60) # ==================== CLI 入口 ==================== def main(): import argparse parser = argparse.ArgumentParser(description="彩票号码分析与机选工具") parser.add_argument("--action", choices=["analyze","recommend","fetch","check","all","report","full_report","backtest"], default="report") parser.add_argument("--lottery", choices=["ssq","dlt","all"], default="all") parser.add_argument("--count", type=int, default=5) parser.add_argument("--query", default="") parser.add_argument("--earnings", type=float, default=0.0, help="中奖收益金额(元),仅check动作有效") parser.add_argument("--target", type=int, default=100, help="全量回滚迭代的目标期数") args = parser.parse_args() p = Persistence() # ==================== full_backtest ==================== if args.action == "backtest": print("\n" + "*"*60) print(" 双色球 & 大乐透 * 智能号码分析系统 v1.0") print(" 全量回滚迭代测试 | 回溯近%d期逐期模拟" % args.target) print("*"*60 + "\n") for lt in (["ssq","dlt"] if args.lottery=="all" else [args.lottery]): bt = FullBacktest(p) result = bt.backtest(lt, target_count=args.target) print("\n[OK] 全量回滚迭代测试完成,详细数据已保存至 feedback.json\n") return if args.action == "fetch": c = Collector(p) c.fetch_all() return if args.action == "check": f = FeedbackLoop(p) for lt in (["ssq","dlt"] if args.lottery=="all" else [args.lottery]): f.check(lt, earnings=args.earnings) return lotteries = ["ssq","dlt"] if args.lottery=="all" else [args.lottery] if args.action == "full_report": print_header() ssq_today, dlt_today = print_today_info() c = Collector(p) c.fetch_all() for lt in lotteries: show_full_report(p, lt, args.count) return if args.action in ["analyze","report","all"]: print_header() ssq_today, dlt_today = print_today_info() # 先采集 c = Collector(p) c.fetch_all() for lt in lotteries: show_analysis(p, lt) if args.action in ["recommend","all","report"]: for lt in lotteries: analyzer = Analyzer(p, lt) if not analyzer.data: print("\n[警告] %s 暂无数据" % LOTTERY_RULES[lt]["name"]) continue show_recommendation(p, lt, args.count) if __name__ == "__main__": main()