Files
MoFin/deploy/profile-scripts/strategy_effectiveness.py

291 lines
11 KiB
Python

#!/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),
}