- 新增 model_full_report_dlt.py: 大乐透专用全量报告脚本 - 统一4块结构: 回滚迭代→数据采集→推理分析→机选号码 - 统一术语: 前区/后区(大乐透) 对齐 红球/蓝球(双色球) - 统一评分: TOP10前区+TOP5后区 归一化概率输出 - 更新 SKILL.md: 双色球/大乐透统一模板+文件结构
260 lines
11 KiB
Python
260 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
大乐透全量报告:与双色球 model_full_report.py 统一4块结构化输出
|
||
使用集成学习模型(5模型融合),输出格式与双色球完全对齐
|
||
"""
|
||
|
||
import json, math, os, datetime, hashlib, random, sys, io
|
||
from collections import Counter
|
||
|
||
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")
|
||
DLT_FILE = os.path.join(DATA_DIR, "dlt_history.json")
|
||
FEEDBACK_FILE = os.path.join(DATA_DIR, "feedback.json")
|
||
CONFIG_FILE = os.path.join(DATA_DIR, "config.json")
|
||
|
||
# ================================================================
|
||
# 加载数据
|
||
# ================================================================
|
||
data = json.load(open(DLT_FILE, "r", encoding="utf-8"))
|
||
records = []
|
||
historical_combos = set()
|
||
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) == 5 and all(1 <= n <= 35 for n in fronts) and len(backs) == 2 and all(1 <= n <= 12 for n in backs):
|
||
records.append({"issue": r["issue"], "fronts": fronts, "backs": backs, "date": r.get("date", "")})
|
||
historical_combos.add(tuple(fronts))
|
||
|
||
records.sort(key=lambda x: int(x["issue"]))
|
||
N = len(records)
|
||
latest = records[-1]
|
||
|
||
# ================================================================
|
||
# 加载回滚统计
|
||
# ================================================================
|
||
config = json.load(open(CONFIG_FILE, "r")) if os.path.exists(CONFIG_FILE) else {"iteration_count": 0}
|
||
feedback = json.load(open(FEEDBACK_FILE, "r")) if os.path.exists(FEEDBACK_FILE) else {
|
||
"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}
|
||
}
|
||
|
||
# ================================================================
|
||
# 计算下期信息
|
||
# ================================================================
|
||
prev_issue = latest["issue"]
|
||
prev_date = latest.get("date", "")
|
||
prev_fronts = latest["fronts"]
|
||
prev_backs = latest["backs"]
|
||
next_issue = str(int(prev_issue) + 1)
|
||
|
||
today = datetime.datetime.now()
|
||
draw_days = [1, 3, 6] # 周一、三、六
|
||
draw_time = "20:25"
|
||
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 = today + datetime.timedelta(days=delta)
|
||
next_draw_str = next_draw.strftime("%Y-%m-%d")
|
||
wd_map = {1:"周一",2:"周二",3:"周三",4:"周四",5:"周五",6:"周六",7:"周日"}
|
||
next_wd = wd_map.get(next_draw.isoweekday(), "")
|
||
|
||
# ================================================================
|
||
# 基础统计分析(与双色球贝叶斯后验对齐)
|
||
# ================================================================
|
||
# 前区频率统计
|
||
all_fronts = []
|
||
for r in records:
|
||
all_fronts.extend(r["fronts"])
|
||
front_freq = Counter(all_fronts)
|
||
total_fronts = len(all_fronts)
|
||
front_probs = {n: front_freq.get(n, 0) / total_fronts for n in range(1, 36)}
|
||
|
||
# 后区频率统计
|
||
all_backs = []
|
||
for r in records:
|
||
all_backs.extend(r["backs"])
|
||
back_freq = Counter(all_backs)
|
||
total_backs = len(all_backs)
|
||
back_probs = {n: back_freq.get(n, 0) / total_backs for n in range(1, 13)}
|
||
|
||
# 前区遗漏统计
|
||
front_last_seen = {n: 0 for n in range(1, 36)}
|
||
for i, r in enumerate(reversed(records)):
|
||
for n in r["fronts"]:
|
||
if front_last_seen[n] == 0:
|
||
front_last_seen[n] = i
|
||
|
||
# 后区遗漏统计
|
||
back_last_seen = {n: 0 for n in range(1, 13)}
|
||
for i, r in enumerate(reversed(records)):
|
||
for n in r["backs"]:
|
||
if back_last_seen[n] == 0:
|
||
back_last_seen[n] = i
|
||
|
||
# 综合评分 = 频率权重 × 0.4 + 遗漏回补 × 0.3 + 近期热度 × 0.3
|
||
front_scores = {}
|
||
for n in range(1, 36):
|
||
freq_score = front_probs.get(n, 0)
|
||
miss_score = min(1.0, front_last_seen[n] / 20) # 遗漏越大,回补概率越高
|
||
# 近期热度:最近10期出现次数
|
||
recent_count = sum(1 for r in records[-10:] if n in r["fronts"])
|
||
hot_score = recent_count / 10
|
||
front_scores[n] = 0.4 * freq_score + 0.3 * miss_score + 0.3 * hot_score
|
||
|
||
back_scores = {}
|
||
for n in range(1, 13):
|
||
freq_score = back_probs.get(n, 0)
|
||
miss_score = min(1.0, back_last_seen[n] / 15)
|
||
recent_count = sum(1 for r in records[-10:] if n in r["backs"])
|
||
hot_score = recent_count / 10
|
||
back_scores[n] = 0.4 * freq_score + 0.3 * miss_score + 0.3 * hot_score
|
||
|
||
# 归一化
|
||
total_front_score = sum(front_scores.values())
|
||
front_probs_norm = {n: s / total_front_score for n, s in front_scores.items()}
|
||
total_back_score = sum(back_scores.values())
|
||
back_probs_norm = {n: s / total_back_score for n, s in back_scores.items()}
|
||
|
||
# ================================================================
|
||
# 加载集成学习模块(抑制中间输出)
|
||
# ================================================================
|
||
sys.path.insert(0, BASE_DIR)
|
||
HAS_ENSEMBLE = False
|
||
ensemble_result = None
|
||
try:
|
||
# 临时重定向stdout以抑制集成学习的中间输出
|
||
old_stdout = sys.stdout
|
||
sys.stdout = io.StringIO()
|
||
from ensemble_learning import ensemble_recommendation
|
||
rule = {"name": "大乐透", "red_max": 35, "red_count": 5, "blue_max": 12, "blue_count": 2,
|
||
"draw_days": [1, 3, 6], "draw_time": "20:25", "source": "中国体育彩票"}
|
||
sorted_hist = sorted(records, key=lambda x: int(x["issue"]), reverse=True)[:100]
|
||
hist_for_ensemble = [{"issue": r["issue"], "reds": r["fronts"], "blue": r["backs"], "date": r.get("date", "")} for r in sorted_hist]
|
||
ensemble_result = ensemble_recommendation("dlt", hist_for_ensemble, rule, count=5)
|
||
sys.stdout = old_stdout
|
||
if ensemble_result and ensemble_result.get("recommendations"):
|
||
HAS_ENSEMBLE = True
|
||
except Exception as e:
|
||
sys.stdout = old_stdout
|
||
pass
|
||
|
||
# ================================================================
|
||
# 4块输出(与双色球统一格式)
|
||
# ================================================================
|
||
print("\n" + "=" * 60)
|
||
print(" [1/4] 回滚迭代")
|
||
print("=" * 60)
|
||
|
||
ver = config.get("iteration_count", 0)
|
||
stats = feedback.get("dlt", {})
|
||
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)
|
||
|
||
print(" 当前版本号: v%d (集成学习模型)" % ver)
|
||
print(" 版本胜率: %s (%d期推荐 / %d次盈利)" % (win_rate, total_recs, profitable))
|
||
print(" 累计中奖收益金额: RMB %.2f" % earnings)
|
||
|
||
# ==================== 第2块:数据采集 ====================
|
||
print("\n" + "=" * 60)
|
||
print(" [2/4] 数据采集")
|
||
print("=" * 60)
|
||
print(" 采集期数范围: 第%s期 ~ 第%s期(%d期)" % (records[0]["issue"], prev_issue, N))
|
||
print(" 上一期号: 第%s期" % prev_issue)
|
||
print(" 开奖日期: %s" % prev_date)
|
||
f_str = " ".join("%02d" % n for n in prev_fronts)
|
||
b_str = " ".join("%02d" % n for n in prev_backs)
|
||
print(" 开奖号码: 前区 %s 后区 %s" % (f_str, b_str))
|
||
|
||
# ==================== 第3块:推理分析 ====================
|
||
print("\n" + "=" * 60)
|
||
print(" [3/4] 推理分析(集成学习模型 | 数据源: 中国体育彩票)")
|
||
print("=" * 60)
|
||
|
||
# 步骤1: 前区频率分析 TOP10(与双色球"贝叶斯后验概率 TOP10红球"对齐)
|
||
top10_front = sorted(front_probs_norm.items(), key=lambda x: -x[1])[:10]
|
||
print(" >> 步骤1 - 频率分析(前区TOP10)")
|
||
print(" %s" % " ".join("%02d(%.4f)" % (n, s) for n, s in top10_front))
|
||
|
||
# 步骤2: 后区分析 TOP5
|
||
top5_back = sorted(back_probs_norm.items(), key=lambda x: -x[1])[:5]
|
||
print(" >> 步骤2 - 后区分析(TOP5)")
|
||
print(" %s" % " ".join("%02d(%.4f)" % (n, s) for n, s in top5_back))
|
||
|
||
# 步骤3: 模式识别
|
||
print(" >> 步骤3 - 模式识别")
|
||
# 统计历史奇偶比和大小比
|
||
oe_counter = Counter()
|
||
bs_counter = Counter()
|
||
for r in records[-30:]:
|
||
odd = sum(1 for n in r["fronts"] if n % 2 == 1)
|
||
even = 5 - odd
|
||
oe_counter[f"{odd}:{even}"] += 1
|
||
big = sum(1 for n in r["fronts"] if n > 17)
|
||
small = 5 - big
|
||
bs_counter[f"{big}:{small}"] += 1
|
||
most_oe = oe_counter.most_common(1)[0][0] if oe_counter else "3:2"
|
||
most_bs = bs_counter.most_common(1)[0][0] if bs_counter else "3:2"
|
||
sum_min = min(r["fronts"][0] + r["fronts"][1] for r in records[-30:])
|
||
sum_max = max(sum(r["fronts"]) for r in records[-30:])
|
||
print(" 奇偶比: %s | 大小比: %s | 和值区间: [%d, %d]" % (most_oe, most_bs, sum_min, sum_max))
|
||
|
||
# 步骤4: 权重体系
|
||
print(" >> 步骤4 - 权重体系(集成学习模型)")
|
||
if HAS_ENSEMBLE and ensemble_result.get("weights"):
|
||
weights = ensemble_result["weights"]
|
||
name_map = {'freq': '频率加权', 'bayes': '贝叶斯平滑', 'markov': '马尔可夫链',
|
||
'kmeans': 'K-Means聚类', 'residual': '残差回归'}
|
||
weight_parts = []
|
||
for m, w in sorted(weights.items(), key=lambda x: -x[1]):
|
||
weight_parts.append("%s%.2f%%" % (name_map.get(m, m), w * 100))
|
||
print(" %s" % " + ".join(weight_parts))
|
||
else:
|
||
print(" 频率40% + 遗漏30% + 热号30%")
|
||
|
||
# ==================== 第4块:机选号码 ====================
|
||
print("\n" + "=" * 60)
|
||
print(" [4/4] 机选号码(第%s期 | %s %s)" % (next_issue, next_draw_str, next_wd))
|
||
print("=" * 60)
|
||
|
||
if HAS_ENSEMBLE and ensemble_result.get("recommendations"):
|
||
recommendations = ensemble_result["recommendations"]
|
||
for i, r in enumerate(recommendations):
|
||
f_str = " ".join("%02d" % n for n in r["reds"])
|
||
b_str = " ".join("%02d" % n for n in r["blues"])
|
||
marker = " ⭐" if i == 0 else ""
|
||
print("\n 第%d组: 前区 %s 后区 %s%s" % (i + 1, f_str, b_str, marker))
|
||
print(" 得分:%.4f | 奇偶:%s | 大小:%s | 和:%d" % (
|
||
r["score"], r["odd_even"], r["big_small"], r["sum"]))
|
||
|
||
# 保存推荐
|
||
analysis_file = os.path.join(DATA_DIR, "analysis_dlt.json")
|
||
analysis = json.load(open(analysis_file, "r")) if os.path.exists(analysis_file) else {}
|
||
analysis["last_recommendation"] = {
|
||
"fronts": recommendations[0]["reds"],
|
||
"backs": recommendations[0]["blues"],
|
||
"generated_at": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
"model_version": "ensemble_v7"
|
||
}
|
||
json.dump(analysis, open(analysis_file, "w"), ensure_ascii=False, indent=2)
|
||
else:
|
||
print(" [⚠️ 无法生成推荐,请检查数据]")
|
||
|
||
print("\n" + "=" * 60)
|
||
print(" ⚠️ 免责声明: 本推荐仅基于历史统计建模的相对概率评分,")
|
||
print(" 不预测下期开奖,不保证中奖。彩票是独立随机事件。")
|
||
print("=" * 60)
|
||
print("\n 开奖后把实际号码发我,帮你回滚迭代校正精度 🍀")
|