#!/usr/bin/env python3 """策略到期评估模块:用完整K线数据评估每个区间的职责履行""" import os, sys, sqlite3, json from datetime import datetime, timedelta # ── 消息通道统一路由(broadcast/xmpp by delivery) ── try: from messenger import install_stdio_hook as _msh _msh() except Exception: pass DB = "/home/hmo/MoFin/data/mofin.db" def get_expired_strategies(conn): """获取已到期且未评估的策略""" now = datetime.now().strftime("%Y-%m-%d") return conn.execute(""" SELECT id, code, version, strategy_source, entry_low, entry_high, stop_loss, take_profit, buy_zone_expected_days, take_profit_expected_days, created_at, superseded_at FROM holding_strategies WHERE status IN ('active', 'superseded') AND superseded_at IS NOT NULL AND id NOT IN (SELECT strategy_id FROM strategy_effectiveness) ORDER BY superseded_at """).fetchall() def get_kline(conn, code, start_date, end_date): """获取K线数据""" return conn.execute(""" SELECT date, open, close, high, low FROM stock_daily WHERE code=? AND date BETWEEN ? AND ? ORDER BY date """, (code, start_date, end_date)).fetchall() def evaluate_zone(klines, zone_type, zone_low, zone_high, expected_days): """评估单个区间的职责履行""" if not klines or zone_low is None or zone_high is None: return {"accuracy": "no_data", "detail": "无K线数据或区间未设定"} first_date = klines[0][0] last_date = klines[-1][0] # 统计每日形态 days_in_zone = 0 # 收盘在区间内 days_penetrated_above = 0 # 收盘突破上沿 days_penetrated_below = 0 # 收盘突破下沿 v_shape_days = 0 # V型(进入后离开) first_trigger_day = None # 首次触发日 prev_close = None for date, open_p, close, high, low in klines: if close is None: continue # 收盘价相对于区间 if zone_low <= close <= zone_high: days_in_zone += 1 if first_trigger_day is None: first_trigger_day = date elif close > zone_high: days_penetrated_above += 1 elif close < zone_low: days_penetrated_below += 1 # V型检测:前一天在区间内,今天离开 if prev_close is not None: if (zone_low <= prev_close <= zone_high) and (close < zone_low or close > zone_high): v_shape_days += 1 prev_close = close total_days = len(klines) actual_days_to_trigger = None if first_trigger_day: fd = datetime.strptime(first_trigger_day, "%Y-%m-%d") sd = datetime.strptime(first_date, "%Y-%m-%d") actual_days_to_trigger = (fd - sd).days # 区间职责评估 if zone_type == "buy_zone": # 买入区职责:触发后是否上涨 if first_trigger_day: trigger_idx = next(i for i, k in enumerate(klines) if k[0] == first_trigger_day) after_klines = klines[trigger_idx:] if after_klines: trigger_close = after_klines[0][2] max_after = max(k[2] for k in after_klines if k[2]) profit_pct = (max_after - trigger_close) / trigger_close * 100 if trigger_close else 0 if profit_pct > 5: accuracy = "effective" elif profit_pct > 0: accuracy = "partially_effective" else: accuracy = "ineffective" else: accuracy = "no_data" else: accuracy = "not_triggered" elif zone_type == "stop_loss": # 止损职责:触发后是否继续跌(避免更大损失) if first_trigger_day: trigger_idx = next(i for i, k in enumerate(klines) if k[0] == first_trigger_day) after_klines = klines[trigger_idx:] if len(after_klines) >= 2: trigger_close = after_klines[0][2] # 之后1-2天是否继续跌 next_2d_low = min(k[4] for k in after_klines[1:3] if k[4]) if next_2d_low and next_2d_low < trigger_close * 0.97: accuracy = "effective" # 止损后继续跌,止损正确 elif next_2d_low and next_2d_low > trigger_close * 1.03: accuracy = "ineffective" # 止损后反弹,被洗盘 else: accuracy = "neutral" else: accuracy = "no_data" else: accuracy = "not_triggered" elif zone_type == "take_profit": # 止盈职责:触发后是否回落(锁定正确) if first_trigger_day: trigger_idx = next(i for i, k in enumerate(klines) if k[0] == first_trigger_day) after_klines = klines[trigger_idx:] if len(after_klines) >= 2: trigger_close = after_klines[0][2] # 之后3天最高价 max_3d = max(k[3] for k in after_klines[1:4] if k[3]) if max_3d and max_3d > trigger_close * 1.05: accuracy = "ineffective" # 止盈后继续涨,偏保守 elif max_3d and max_3d < trigger_close: accuracy = "effective" # 止盈后回落,锁定正确 else: accuracy = "neutral" else: accuracy = "no_data" else: accuracy = "not_triggered" # 时间准确性 if actual_days_to_trigger and expected_days: ratio = actual_days_to_trigger / expected_days if ratio <= 1: time_accuracy = "on_time" elif ratio <= 1.5: time_accuracy = "slightly_late" else: time_accuracy = "late" else: time_accuracy = "no_data" return { "accuracy": accuracy, "time_accuracy": time_accuracy, "actual_days": actual_days_to_trigger, "expected_days": expected_days, "days_in_zone": days_in_zone, "v_shape_days": v_shape_days, "total_days": total_days, } def evaluate_strategy(conn, strategy): """评估单个策略""" sid, code, ver, source, el, eh, sl, tp, buy_exp, tp_exp, created, superseded = strategy # 获取K线数据(从创建到被替代) klines = get_kline(conn, code, created[:10], superseded[:10]) if not klines: return None buy_eval = evaluate_zone(klines, "buy_zone", el, eh, buy_exp) tp_eval = evaluate_zone(klines, "take_profit", tp, tp*1.05 if tp else None, tp_exp) if tp else None sl_eval = evaluate_zone(klines, "stop_loss", sl*0.95 if sl else None, sl, None) if sl else None # 综合评价 assessment = [] if buy_eval["accuracy"] == "ineffective": assessment.append("买入区预判有误(触发后下跌)") elif buy_eval["accuracy"] == "effective": assessment.append("买入区预判准确") if sl_eval and sl_eval["accuracy"] == "ineffective": assessment.append("止损被洗盘(触发后反弹)") elif sl_eval and sl_eval["accuracy"] == "effective": assessment.append("止损有效(避免更大损失)") if tp_eval and tp_eval["accuracy"] == "ineffective": assessment.append("止盈偏保守(触发后继续涨)") elif tp_eval and tp_eval["accuracy"] == "effective": assessment.append("止盈锁定正确") if buy_eval["time_accuracy"] == "late": assessment.append("入场时机偏晚") elif buy_eval["time_accuracy"] == "on_time": assessment.append("入场时机准确") # 改进建议 suggestions = [] if buy_eval["accuracy"] == "ineffective": suggestions.append("买入区需要下调(当前价位偏高)") if buy_eval["time_accuracy"] == "late": suggestions.append("买入区预期天数需要延长") if sl_eval and sl_eval["accuracy"] == "ineffective": suggestions.append("止损位设置过于激进,考虑放宽") if tp_eval and tp_eval["accuracy"] == "ineffective": suggestions.append("止盈位偏保守,可适当提高") return { "strategy_id": sid, "code": code, "strategy_source": source, "version": ver, "period_start": created[:10], "period_end": superseded[:10], "buy_zone_accuracy": buy_eval["accuracy"], "take_profit_accuracy": tp_eval["accuracy"] if tp_eval else "no_zone", "stop_loss_accuracy": sl_eval["accuracy"] if sl_eval else "no_zone", "time_accuracy": buy_eval["time_accuracy"], "overall_assessment": "; ".join(assessment) if assessment else "数据不足", "improvement_suggestion": "; ".join(suggestions) if suggestions else "暂无改进建议", } def main(): conn = sqlite3.connect(DB, timeout=30) expired = get_expired_strategies(conn) print(f"待评估策略: {len(expired)} 个") ok = 0 for strategy in expired: result = evaluate_strategy(conn, strategy) if result: conn.execute(""" INSERT INTO strategy_effectiveness (strategy_id, code, strategy_source, version, period_start, period_end, buy_zone_accuracy, take_profit_accuracy, stop_loss_accuracy, time_accuracy, overall_assessment, improvement_suggestion) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) """, (result["strategy_id"], result["code"], result["strategy_source"], result["version"], result["period_start"], result["period_end"], result["buy_zone_accuracy"], result["take_profit_accuracy"], result["stop_loss_accuracy"], result["time_accuracy"], result["overall_assessment"], result["improvement_suggestion"])) ok += 1 print(f" {result['code']} v{result['version']}: {result['overall_assessment'][:50]}") conn.commit() conn.close() print(f"\n评估完成: {ok} 个策略已评估") if __name__ == "__main__": main() # ── 扩展评估:读取 recommendation_log + execution_log ── def evaluate_with_logs(conn, strategy): """用推荐记录+执行记录增强评估""" sid = strategy[0] code = strategy[1] # 读推荐记录 recs = conn.execute( "SELECT recommend_time, action, entry_low, entry_high, stop_loss, take_profit " "FROM recommendation_log WHERE strategy_id=? ORDER BY recommend_time", (sid,)).fetchall() # 读执行记录 execs = conn.execute( "SELECT execute_time, action, shares, price FROM execution_log " "WHERE code=? ORDER BY execute_time", (code,)).fetchall() # 匹配:推荐后是否有对应执行 rec_followed = 0 rec_not_followed = 0 for rec in recs: rec_time, rec_action = rec[0], rec[1] found = False for exe in execs: if exe[0] >= rec_time and exe[1] == rec_action: found = True rec_followed += 1 break if not found: rec_not_followed += 1 compliance_rate = rec_followed / (rec_followed + rec_not_followed) * 100 if (rec_followed + rec_not_followed) > 0 else 0 return { "recommendations": len(recs), "executions_matched": rec_followed, "executions_missed": rec_not_followed, "compliance_rate": round(compliance_rate, 1), }