- 归档strategy_evaluator.py/advice_reconciliation.py(功能已被strategy_effectiveness替代) - strategy_effectiveness扩展: 读取recommendation_log+execution_log增强评估 - mofin_db新增: log_recommendation/log_execution/match_recommendations_executions - 新表recommendation_log(推荐历史)+execution_log(执行历史)
284 lines
11 KiB
Python
284 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""策略到期评估模块:用完整K线数据评估每个区间的职责履行"""
|
|
|
|
import os, sys, sqlite3, json
|
|
from datetime import datetime, timedelta
|
|
|
|
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),
|
|
}
|