alphax/app/db/review_basic_queries.py
2026-05-22 16:24:54 +08:00

112 lines
4.5 KiB
Python

"""Basic review write/read helpers separated from strategy-iteration logic."""
import json
from datetime import datetime
from app.db.schema import get_conn
def record_review(rec_id, symbol, outcome, pnl_48h, max_pnl_48h,
triggered_signals, hit_signals, miss_signals, lesson):
"""Insert one recommendation review row."""
conn = get_conn()
conn.execute("""
INSERT INTO review_log (rec_id, symbol, review_time, outcome, pnl_48h, max_pnl_48h,
triggered_signals, hit_signals, miss_signals, lesson)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (
rec_id, symbol, datetime.now().isoformat(), outcome, pnl_48h, max_pnl_48h,
json.dumps(triggered_signals, ensure_ascii=False) if isinstance(triggered_signals, list) else triggered_signals,
json.dumps(hit_signals, ensure_ascii=False) if isinstance(hit_signals, list) else hit_signals,
json.dumps(miss_signals, ensure_ascii=False) if isinstance(miss_signals, list) else miss_signals,
lesson,
))
conn.commit()
conn.close()
def update_signal_performance(signal_type, category, is_hit, pnl, weight_override=None):
"""Update rolling signal performance stats after review.
``weight_override`` is used by the daily review governance step to make
reviewed factor weights actually affect the next screening run.
"""
conn = get_conn()
row = conn.execute("SELECT * FROM signal_performance WHERE signal_type=%s", (signal_type,)).fetchone()
if weight_override is not None:
weight = max(0.0, float(weight_override or 0))
if row:
conn.execute(
"""
UPDATE signal_performance
SET weight=%s, last_updated=%s
WHERE signal_type=%s
""",
(weight, datetime.now().isoformat(), signal_type),
)
else:
conn.execute(
"""
INSERT INTO signal_performance (
signal_type, category, total_count, hit_count, miss_count,
hit_rate, avg_pnl, weight, last_updated
) VALUES (%s, %s, 0, 0, 0, 0, 0, %s, %s)
""",
(signal_type, category or "", weight, datetime.now().isoformat()),
)
conn.commit()
conn.close()
return
if row:
total = row["total_count"] + 1
hits = row["hit_count"] + (1 if is_hit else 0)
misses = row["miss_count"] + (0 if is_hit else 1)
old_avg_pnl = row["avg_pnl"]
new_avg_pnl = round((old_avg_pnl * (total - 1) + pnl) / total, 2)
hit_rate = round(hits / total * 100, 1) if total > 0 else 0
conn.execute("""
UPDATE signal_performance SET total_count=%s, hit_count=%s, miss_count=%s,
hit_rate=%s, avg_pnl=%s, weight=%s, last_updated=%s
WHERE signal_type=%s
""", (total, hits, misses, hit_rate, new_avg_pnl, hit_rate / 50, datetime.now().isoformat(), signal_type))
else:
conn.execute("""
INSERT INTO signal_performance (signal_type, category, total_count, hit_count, miss_count,
hit_rate, avg_pnl, weight, last_updated)
VALUES (%s, %s, 1, %s, %s, %s, %s, %s, %s)
""", (
signal_type, category, 1 if is_hit else 0, 0 if is_hit else 1,
100 if is_hit else 0, pnl, 2.0 if is_hit else 0, datetime.now().isoformat(),
))
conn.commit()
conn.close()
def get_signal_weights():
"""Read dynamic signal weights."""
conn = get_conn()
rows = conn.execute("SELECT signal_type, category, weight, hit_rate, avg_pnl, total_count FROM signal_performance").fetchall()
conn.close()
return {row["signal_type"]: dict(row) for row in rows}
def record_missed_explosion(symbol, price_at_detect, price_before, gain_pct,
reason_missed, features_detected, lesson):
"""Insert one missed-explosion review row."""
conn = get_conn()
conn.execute("""
INSERT INTO missed_explosions (symbol, detect_time, price_at_detect, price_before,
gain_pct, reason_missed, features_detected, lesson)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
""", (
symbol, datetime.now().isoformat(), price_at_detect, price_before, gain_pct,
json.dumps(reason_missed, ensure_ascii=False) if isinstance(reason_missed, list) else reason_missed,
json.dumps(features_detected, ensure_ascii=False) if isinstance(features_detected, list) else features_detected,
lesson,
))
conn.commit()
conn.close()