# 웹 호출 라이브러리를 호출합니다. import time import requests import json import os import copy import pandas as pd import itertools from datetime import datetime, timedelta from TelegramBot import TelegramBot from filter_model_3 import BallFilter class Practice: bot = None preprocessor = None predictor = None extract_count = None TARGET_MIN_SURVIVORS = 30 TARGET_MAX_SURVIVORS = 150 PREDICT_TIMEOUT_SECONDS = 180 def __init__(self, resources_path): self.bot = TelegramBot() self.resources_path = resources_path return # 로또 당첨 데이터를 수집해서 파일로 저장합니다. # lottoHistoryFile: 로또 당첨 데이터를 저장할 파일 def craw(self, lottoHistoryFile, drwNo=None): ball = None if drwNo != None: # 로또 데이터를 저장할 파일을 선언합니다. jsonFp = open(lottoHistoryFile + ".json", 'a', encoding="utf-8") textFp = open(lottoHistoryFile + ".txt", 'a', encoding="utf-8") url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(drwNo) # URL을 호출합니다. res = requests.post(url) # 호출한 결과에 대해서 Json 포맷을 가져옵니다. result = res.json() if result['returnValue'] != 'success': return None # 가져온 Json 포맷을 파일로 저장합니다. jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) print("%d,%d,%d,%d,%d,%d,%d,%d" % (drwNo, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] else: # 로또 데이터를 저장할 파일을 선언합니다. jsonFp = open(lottoHistoryFile + ".json", 'w', encoding="utf-8") textFp = open(lottoHistoryFile + ".txt", 'w', encoding="utf-8") # 1회차부터 지정된 회차까지 로또 당첨 번호를 수집합니다. idx = 1 while True: # 1회차부터 지정된 회차까지의 URL을 생성합니다. url = 'https://dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=' + str(idx) # URL을 호출합니다. res = requests.post(url) # 호출한 결과에 대해서 Json 포맷을 가져옵니다. result = res.json() if result['returnValue'] != 'success': break # 가져온 Json 포맷을 파일로 저장합니다. jsonFp.write(json.dumps(result, ensure_ascii=False) + "\n") textFp.write("%d,%d,%d,%d,%d,%d,%d,%d\n" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) print("%d,%d,%d,%d,%d,%d,%d,%d" % (idx, result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo'])) ball = [result['drwtNo1'], result['drwtNo2'], result['drwtNo3'], result['drwtNo4'], result['drwtNo5'], result['drwtNo6'], result['bnusNo']] idx += 1 time.sleep(0.5) # 저장한 파일을 종료합니다. jsonFp.close() textFp.close() return ball def predict1(self, result_json): result_json.append([6, 7, 10, 11, 20, 45]) result_json.append([2, 7, 17, 28, 35, 39]) result_json.append([6, 10, 19, 25, 33, 35]) result_json.append([3, 17, 20, 24, 35, 45]) result_json.append([5, 15, 18, 29, 36, 41]) result_json.append([6, 15, 20, 23, 37, 43]) result_json.append([8, 15, 19, 23, 38, 41]) result_json.append([5, 11, 19, 24, 40, 45]) result_json.append([9, 16, 18, 23, 35, 43]) result_json.append([7, 13, 19, 28, 33, 44]) result_json.append([7, 11, 18, 29, 37, 42]) return def predict2(self, resources_path, ymd, result_json): candidates = [i for i in range(1, 46)] lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.json') no = BallFilter(lottoHistoryFileName).getNextNo(ymd) print("회차: {}".format(no)) predict_start_ts = time.time() deadline_ts = predict_start_ts + self.PREDICT_TIMEOUT_SECONDS lottoHistoryFileName = os.path.join(resources_path, 'lotto_history.txt') df_ball = pd.read_csv(lottoHistoryFileName, header=None) df_ball.columns = ['no', 'b1', 'b2', 'b3', 'b4', 'b5', 'b6', 'bn'] p_ball = df_ball[df_ball['no'] == no - 1].values.tolist()[0] p_no = p_ball[0] p_ball = sorted(p_ball[1:7]) # 기본/강화/완화 단계별 ruleset base_ruleset = self._get_base_ruleset() tighten_rulesets = [ self._build_ruleset( base_ruleset=base_ruleset, enabled_overrides={ "paper_patterns": True, "ban_triples_legacy": True, "all_in_previous7": True, "previous_neighbors": True, }, allowed_overrides={ "ac_value": [8, 9], "uniq_last_digit_count": [4, 5], "even_count": [2, 3, 4], }, ), self._build_ruleset( base_ruleset=base_ruleset, enabled_overrides={ "paper_patterns": True, "ban_triples_legacy": True, "all_in_previous7": True, "previous_neighbors": True, }, allowed_overrides={ "ac_value": [8, 9], "uniq_last_digit_count": [4, 5], "even_count": [2, 3, 4], "sum": [112, 114, 121, 123, 126, 127, 131, 132, 138, 146, 148], "sum_prev_diff": [13, 14, 17, 18, 26, 28, 29, 30, 32, 39, 40], }, ), ] relax_rulesets = [ self._build_ruleset( base_ruleset=base_ruleset, enabled_overrides={ "paper_patterns": False, "ban_triples_legacy": False, }, ), self._build_ruleset( base_ruleset=base_ruleset, enabled_overrides={ "paper_patterns": False, "ban_triples_legacy": False, "previous_neighbors": False, "all_in_previous7": False, }, ), self._build_ruleset( base_ruleset=base_ruleset, enabled_overrides={ "paper_patterns": False, "ban_triples_legacy": False, "previous_neighbors": False, "all_in_previous7": False, "weeks_8_count": False, "weeks_12_count": False, "weeks_16_count": False, "weeks_20_count": False, }, ), ] min_survivors = self.TARGET_MIN_SURVIVORS max_survivors = self.TARGET_MAX_SURVIVORS chosen = [] stage_name = "base" current_info = self._collect_candidates( candidates=candidates, no=no, df_ball=df_ball, ruleset=base_ruleset, stop_when_gt=max_survivors, stage_name="base", predict_start_ts=predict_start_ts, deadline_ts=deadline_ts, ) current = current_info["candidates"] if current_info["timed_out"]: chosen = self._finalize_on_timeout(current, p_ball, min_survivors, max_survivors) stage_name = "base_timeout_fallback" print("candidate_stage: {}, survivors: {}".format(stage_name, len(chosen))) for ball in chosen: result_json.append(ball) return p_no, p_ball if min_survivors <= len(current) <= max_survivors: chosen = current elif len(current) > max_survivors: chosen = current stage_name = "base_overflow" for idx, rs in enumerate(tighten_rulesets, start=1): t_info = self._collect_candidates( candidates=candidates, no=no, df_ball=df_ball, ruleset=rs, stop_when_gt=max_survivors, stage_name="tighten_{}".format(idx), predict_start_ts=predict_start_ts, deadline_ts=deadline_ts, ) t = t_info["candidates"] if t_info["timed_out"]: chosen = self._finalize_on_timeout(t, p_ball, min_survivors, max_survivors) stage_name = "tighten_{}_timeout_fallback".format(idx) break if min_survivors <= len(t) <= max_survivors: chosen = t stage_name = "tighten_{}".format(idx) break if len(t) <= max_survivors: chosen = t stage_name = "tighten_{}".format(idx) if len(chosen) > max_survivors: # 상한 가드 강제 적용: 품질 점수 상위 N개만 사용 full_info = self._collect_candidates( candidates=candidates, no=no, df_ball=df_ball, ruleset=tighten_rulesets[-1], stop_when_gt=None, stage_name="tighten_full_rank", predict_start_ts=predict_start_ts, deadline_ts=deadline_ts, ) full_for_ranking = full_info["candidates"] if full_info["timed_out"]: chosen = self._finalize_on_timeout(full_for_ranking, p_ball, min_survivors, max_survivors) stage_name = "tighten_rank_timeout_fallback" else: chosen = self._rank_and_trim(full_for_ranking, p_ball, max_survivors) stage_name = "tighten_rank_trim" else: chosen = current stage_name = "base_underflow" for idx, rs in enumerate(relax_rulesets, start=1): # relax는 하한(min_survivors)만 채우면 충분하므로 조기 종료 r_info = self._collect_candidates( candidates=candidates, no=no, df_ball=df_ball, ruleset=rs, stop_when_gt=None, stop_when_gte=min_survivors, stage_name="relax_{}".format(idx), predict_start_ts=predict_start_ts, deadline_ts=deadline_ts, ) r = r_info["candidates"] chosen = r stage_name = "relax_{}".format(idx) if r_info["timed_out"]: chosen = self._finalize_on_timeout(r, p_ball, min_survivors, max_survivors) stage_name = "relax_{}_timeout_fallback".format(idx) break if len(r) >= min_survivors: break if len(chosen) == 0: # 0개 생존 방지: 가장 완화된 규칙에서도 0개면 직전 결과와 유사한 조합으로 최소 개수 확보 stage_name = "relax_zero_fallback" chosen = self._fallback_candidates_from_prev(p_ball, min_survivors) elif len(chosen) < min_survivors: # 하한 가드: 부족분은 완화 후보/고정 후보 기반으로 보강 stage_name = "{}_fill".format(stage_name) fill = self._fallback_candidates_from_prev(p_ball, min_survivors - len(chosen), exclude=set(tuple(x) for x in chosen)) chosen.extend(fill) print("candidate_stage: {}, survivors: {}".format(stage_name, len(chosen))) for ball in chosen: result_json.append(ball) return p_no, p_ball def _get_base_ruleset(self): history_json = os.path.join(self.resources_path, "lotto_history.json") base_filter = BallFilter(history_json) return copy.deepcopy(base_filter.m1.ruleset) def _build_ruleset(self, base_ruleset, enabled_overrides=None, allowed_overrides=None): ruleset = copy.deepcopy(base_ruleset) ruleset.setdefault("filters", {}) enabled_overrides = enabled_overrides or {} allowed_overrides = allowed_overrides or {} for key, value in enabled_overrides.items(): ruleset["filters"].setdefault(key, {}) ruleset["filters"][key]["enabled"] = bool(value) for key, values in allowed_overrides.items(): ruleset["filters"].setdefault(key, {}) ruleset["filters"][key]["enabled"] = True ruleset["filters"][key]["allowed"] = list(values) return ruleset def _collect_candidates( self, candidates, no, df_ball, ruleset, stop_when_gt=None, stop_when_gte=None, stage_name="base", predict_start_ts=None, deadline_ts=None, ): lottoHistoryFileName = os.path.join(self.resources_path, "lotto_history.json") ballFilter = BallFilter(lottoHistoryFileName, ruleset=ruleset) result = [] last_idx = 0 for idx, ball in enumerate(itertools.combinations(candidates, 6), start=1): last_idx = idx if deadline_ts is not None and deadline_ts <= time.time(): elapsed = (time.time() - predict_start_ts) if predict_start_ts is not None else 0.0 print(" - [{}] timeout after {:,} processed (elapsed: {:.1f}s, survivors: {:,})".format(stage_name, idx, elapsed, len(result))) return { "candidates": result, "timed_out": True, "processed": idx, } if idx % 1000000 == 0: elapsed = (time.time() - predict_start_ts) if predict_start_ts is not None else 0.0 print(" - [{}] {:,} processed... (elapsed: {:.1f}s, survivors: {:,})".format(stage_name, idx, elapsed, len(result))) b = list(ball) if len(ballFilter.filter(ball=b, no=no, until_end=False, df=df_ball)) == 0: result.append(b) if stop_when_gt is not None and len(result) > stop_when_gt: return { "candidates": result, "timed_out": False, "processed": idx, } if stop_when_gte is not None and len(result) >= stop_when_gte: return { "candidates": result, "timed_out": False, "processed": idx, } return { "candidates": result, "timed_out": False, "processed": last_idx, } def _finalize_on_timeout(self, partial_candidates, prev_ball, min_survivors, max_survivors): chosen = list(partial_candidates) if len(chosen) > max_survivors: chosen = self._rank_and_trim(chosen, prev_ball, max_survivors) elif len(chosen) < min_survivors: fill = self._fallback_candidates_from_prev( prev_ball, min_survivors - len(chosen), exclude=set(tuple(x) for x in chosen), ) chosen.extend(fill) return chosen def _rank_and_trim(self, candidates, prev_ball, limit): scored = [(self._score_candidate(ball, prev_ball), ball) for ball in candidates] scored.sort(key=lambda x: x[0]) return [ball for _, ball in scored[:limit]] def _score_candidate(self, ball, prev_ball): sum_diff = abs(sum(ball) - sum(prev_ball)) even_cnt = len([x for x in ball if x % 2 == 0]) uniq_last = len(set([x % 10 for x in ball])) contiguous_penalty = 0 s = sorted(ball) for i in range(1, len(s)): if s[i] - s[i - 1] == 1: contiguous_penalty += 1 score = 0 score += sum_diff score += abs(even_cnt - 3) * 2 score += abs(uniq_last - 5) * 2 score += contiguous_penalty return score def _fallback_candidates_from_prev(self, prev_ball, need_count, exclude=None): exclude = exclude or set() seed = sorted(prev_ball) out = [] delta_patterns = [ (0, 0, 0, 0, 0, 0), (-1, 0, 0, 0, 0, 1), (0, -1, 0, 0, 1, 0), (0, 0, -1, 1, 0, 0), (-2, 0, 0, 0, 0, 2), (0, -2, 0, 0, 2, 0), (0, 0, -2, 2, 0, 0), (-1, -1, 0, 0, 1, 1), (1, 0, -1, 0, 0, 0), (0, 1, 0, -1, 0, 0), (1, -1, 1, -1, 1, -1), (-1, 1, -1, 1, -1, 1), ] shift = 0 while len(out) < need_count and shift <= 8: for delta in delta_patterns: cand = [seed[i] + delta[i] for i in range(6)] cand = [min(45, max(1, v + shift)) for v in cand] cand = sorted(cand) if len(set(cand)) != 6: continue t = tuple(cand) if t in exclude: continue exclude.add(t) out.append(cand) if len(out) >= need_count: break shift += 1 return out if __name__ == '__main__': PROJECT_HOME = '.' resources_path = os.path.join(PROJECT_HOME, 'resources') today = datetime.today() if today.weekday() == 5: if today.hour > 20: this_weekend = today + timedelta(days=(12 - today.weekday())) else: this_weekend = today + timedelta(days=(5 - today.weekday())) elif today.weekday() == 6: this_weekend = today + timedelta(days=(12 - today.weekday())) else: this_weekend = today + timedelta(days=(5 - today.weekday())) last_weekend = (this_weekend - timedelta(days=7)).strftime('%Y%m%d') ymd = this_weekend.strftime('%Y%m%d') print("ymd: {}".format(ymd)) # 로또 예측 practice = Practice(resources_path) # 데이터 수집 lottoHistoryFile = PROJECT_HOME + '/resources/lotto_history' lottoHistoryFileName = lottoHistoryFile + '.json' with open(lottoHistoryFileName, "r", encoding='utf-8') as f: for line in f: if line != '\n': last_json = json.loads(line) #ball = practice.craw(lottoHistoryFile, drwNo=last_json['drwNo'] + 1) result_json = {ymd: []} # 매주 고정 practice.predict1(result_json[ymd]) # 필터 기반 예측 p_no, p_ball = practice.predict2(resources_path, ymd, result_json[ymd]) p_str = "[지난주] {}\n - {} 회차, {}\n[금주] {}\n - {} 회차\n[모델#25]\n".format(last_weekend, p_no, str(p_ball), ymd, (p_no + 1)) for i, ball in enumerate(result_json[ymd]): p_str += " {}. {}\n".format((i+1), str(ball)) if (i+1) % 100 == 0: practice.bot.sendMsg("{}".format(p_str)) p_str = '' if len(result_json[ymd]) % 100 != 0: practice.bot.sendMsg("{}".format(p_str)) size = len(result_json[ymd]) print("size: {}".format(size)) # https://youtu.be/QjBsui8Ob14?si=4dC3q8p0Yu5ZWK1K # https://www.youtube.com/watch?v=YwiHaa1KNwA print("done...")