feat: 彩票技能统一输出格式 - 双色球/大乐透4块结构化输出标准对齐
- 新增 model_full_report_dlt.py: 大乐透专用全量报告脚本 - 统一4块结构: 回滚迭代→数据采集→推理分析→机选号码 - 统一术语: 前区/后区(大乐透) 对齐 红球/蓝球(双色球) - 统一评分: TOP10前区+TOP5后区 归一化概率输出 - 更新 SKILL.md: 双色球/大乐透统一模板+文件结构
This commit is contained in:
1 parent
f45881dc88
commit
c4bb8632ca
15 files changed
+9932
No files matched your search
@@ -0,0 +1,259 @@
|
||||
#!/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 开奖后把实际号码发我,帮你回滚迭代校正精度 🍀")
|
||||
Reference in new issue
Block a user