#!/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()