From 03056a47478b826cad09909f29688715f4e0c789 Mon Sep 17 00:00:00 2001 From: gahusb Date: Sun, 31 May 2026 17:33:27 +0900 Subject: [PATCH] =?UTF-8?q?feat(lotto):=20evaluate=5Fweekly=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5=20=EC=8B=A0=ED=98=B8=EB=A5=BC=20forward=20lift?= =?UTF-8?q?=EB=A1=9C=20=EC=8A=B9=EA=B2=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- lotto/app/weight_evolver.py | 29 +++++++++++++++++++++++++++++ lotto/tests/test_weight_evolver.py | 7 +++++++ 2 files changed, 36 insertions(+) diff --git a/lotto/app/weight_evolver.py b/lotto/app/weight_evolver.py index c5bf308..2ff91fb 100644 --- a/lotto/app/weight_evolver.py +++ b/lotto/app/weight_evolver.py @@ -294,6 +294,35 @@ def evaluate_weekly() -> Dict[str, Any]: winner = max(per_day, key=lambda d: d["avg_score"]) + # 자가학습 강화: backtest forward 등수점수 lift로 winner 재선정 + latest_no = latest["drw_no"] + runs = db.get_backtest_runs(draw_no=latest_no) + engine_runs = [r for r in runs if r["strategy"] == "engine_w"] + null_runs = [r for r in runs if r["strategy"] == "random_null"] + if engine_runs and null_runs: + random_score = prize_score_from_hist(null_runs[0]) + per_w = [] + for r in engine_runs: + per_w.append({ + "trial_id": r["trial_id"], + "day_of_week": int(r["weight_label"][1:]) if r["weight_label"].startswith("w") else 0, + "weight": json.loads(r["weight_json"]) if r["weight_json"] else DEFAULT_UNIFORM[:], + "prize_score": prize_score_from_hist(r), + }) + lift_winner = select_winner_by_lift(per_w, random_score=random_score) + if not lift_winner["gated"]: + winner = { + "trial_id": lift_winner["trial_id"], + "day_of_week": lift_winner["day_of_week"], + "weight": lift_winner["weight"], + "avg_score": winner["avg_score"], + "max_correct": winner["max_correct"], + "lift": lift_winner["lift"], + } + else: + # 노이즈 → base 유지 강제 (max_correct를 0으로 낮춰 unchanged 유도) + winner = {**winner, "max_correct": min(winner["max_correct"], 2), "lift": lift_winner["lift"]} + current_base = db.get_current_base() new_base, reason = decide_base_update( winner_max_correct=winner["max_correct"], diff --git a/lotto/tests/test_weight_evolver.py b/lotto/tests/test_weight_evolver.py index 3ac3f0e..2d642f5 100644 --- a/lotto/tests/test_weight_evolver.py +++ b/lotto/tests/test_weight_evolver.py @@ -135,3 +135,10 @@ def test_select_winner_by_lift_gating(): winner2 = we.select_winner_by_lift(per_w, random_score=3.0, epsilon=2.0) assert winner2["gated"] is False assert winner2["trial_id"] == 2 # prize 9 → lift +6 + + +def test_prize_score_from_hist(): + # 등수 가중치: 1등 매우 큼, 하위는 작게 + s = we.prize_score_from_hist({"m3": 10, "m4": 2, "m5": 0, "m6": 0, "bonus_hits": 0}) + s_big = we.prize_score_from_hist({"m3": 0, "m4": 0, "m5": 0, "m6": 1, "bonus_hits": 0}) + assert s_big > s # 1등 1장이 5등 다수보다 큼