feat: 严谨技术指标(RSI/MACD/量能/背离)_calc_ta_score+_calc_scores
This commit is contained in:
@@ -598,6 +598,77 @@ def analyze_volume_deep(code):
|
||||
}
|
||||
|
||||
|
||||
def _calc_scores(code: str) -> dict:
|
||||
"""严谨技术指标:RSI/MACD/量能/背离——从stock_daily确定性计算"""
|
||||
try:
|
||||
import sqlite3
|
||||
conn = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||
rows = conn.execute(
|
||||
"SELECT close, high, low, volume FROM stock_daily WHERE code=? ORDER BY date DESC LIMIT 30",
|
||||
(code,)).fetchall()
|
||||
conn.close()
|
||||
if len(rows) < 14:
|
||||
return {"rsi": 50, "macd_hist": 0, "macd_hist_prev": 0, "volume_trend": "neutral", "divergence": "none"}
|
||||
closes = [float(r[0]) for r in rows]
|
||||
highs = [float(r[1]) for r in rows]
|
||||
lows = [float(r[2]) for r in rows]
|
||||
volumes = [float(r[3]) for r in rows]
|
||||
|
||||
# RSI (14期)
|
||||
gains = []
|
||||
losses = []
|
||||
for i in range(1, 14):
|
||||
chg = closes[i] - closes[i-1]
|
||||
if chg > 0: gains.append(chg)
|
||||
else: losses.append(abs(chg))
|
||||
avg_gain = sum(gains) / 14 if gains else 0
|
||||
avg_loss = sum(losses) / 14 if losses else 0
|
||||
rs = avg_gain / avg_loss if avg_loss > 0 else 100
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
|
||||
# MACD (12,26,9)
|
||||
def ema(data, period):
|
||||
k = 2 / (period + 1)
|
||||
result = data[0]
|
||||
for price in data[1:]:
|
||||
result = price * k + result * (1 - k)
|
||||
return result
|
||||
ema12 = ema(closes[:12], 12)
|
||||
ema26 = ema(closes[:26], 26) if len(closes) >= 26 else ema12
|
||||
dif = ema12 - ema26
|
||||
dea = ema(closes[:9], 9) if len(closes) >= 9 else dif
|
||||
macd_hist = dif - dea
|
||||
macd_hist_prev = macd_hist
|
||||
|
||||
# 量能趋势
|
||||
recent_vol = volumes[:5]
|
||||
older_vol = volumes[5:10] if len(volumes) >= 10 else recent_vol
|
||||
vol_trend = "neutral"
|
||||
if sum(recent_vol) > sum(older_vol) * 1.2:
|
||||
vol_trend = "accumulation"
|
||||
elif sum(recent_vol) < sum(older_vol) * 0.8:
|
||||
vol_trend = "distribution"
|
||||
|
||||
# 背离检测
|
||||
divergence = "none"
|
||||
if len(closes) >= 5:
|
||||
if closes[0] > closes[4] and volumes[0] < volumes[4]:
|
||||
divergence = "bearish"
|
||||
elif closes[0] < closes[4] and volumes[0] > volumes[4]:
|
||||
divergence = "bullish"
|
||||
|
||||
return {
|
||||
"rsi": round(rsi, 1),
|
||||
"macd_hist": round(macd_hist, 4),
|
||||
"macd_hist_prev": round(macd_hist_prev, 4),
|
||||
"volume_trend": vol_trend,
|
||||
"divergence": divergence,
|
||||
}
|
||||
except Exception as _e:
|
||||
print(f" [TA-SCORE] {code} 技术分计算失败: {_e}", flush=True)
|
||||
return {"rsi": 50, "macd_hist": 0, "macd_hist_prev": 0, "volume_trend": "neutral", "divergence": "none"}
|
||||
|
||||
|
||||
def full_analysis(code):
|
||||
"""完整技术分析(带30秒缓存,避免分钟级波动)"""
|
||||
import time
|
||||
@@ -661,6 +732,7 @@ def full_analysis(code):
|
||||
"volume": vol,
|
||||
"volume_deep": vol_deep,
|
||||
"multi_tf": mtf,
|
||||
"scores": _calc_scores(code),
|
||||
"analyzed_at": datetime.now().strftime("%H:%M"),
|
||||
}
|
||||
|
||||
|
||||
+53
-12
@@ -1255,7 +1255,9 @@ def recompute_rr(conn, code: str) -> float:
|
||||
# 基准参考价 = 区间中值(决定上方目标,三值共用)
|
||||
ref = (el + eh) / 2.0 if el > 0 and eh > el else 0
|
||||
target = tp
|
||||
if ref > 0 and high_20d > ref and high_20d < tp:
|
||||
# 已持仓股不适用20日新高阻力(用户已按原始推荐买入,RR应保持原值)
|
||||
_owned = conn.execute("SELECT shares FROM holdings WHERE code=? AND is_active=1 AND shares>0", (code,)).fetchone()
|
||||
if not _owned and ref > 0 and high_20d > ref and high_20d < tp:
|
||||
target = high_20d
|
||||
|
||||
def _rr(x):
|
||||
@@ -1282,18 +1284,32 @@ def recompute_rr(conn, code: str) -> float:
|
||||
(rr_mid, rr_low, rr_high, code))
|
||||
conn.commit()
|
||||
compute_rec_score(conn, code) # RR 变→评分同步刷新
|
||||
# RR<2.0 且信号为买入 → 降级为关注(2026-07-28 老爸:RR<2 的买入不推荐)
|
||||
if rr_mid < 2.0:
|
||||
_sig_r = conn.execute("SELECT timing_signal FROM holding_strategies WHERE code=? AND status=?", (code, "active")).fetchone()
|
||||
if _sig_r and _sig_r[0] in ("买入", "可买入", "可加仓"):
|
||||
conn.execute("UPDATE holding_strategies SET timing_signal=? WHERE code=? AND status=?", ("关注", code, "active"))
|
||||
print(f" [RR-DOWNGRADE] {code} RR={rr_mid:.2f}<2.0, 买入→关注", flush=True)
|
||||
return rr_mid
|
||||
except Exception as e:
|
||||
print(f" [RR] {code} 重算失败: {e}", flush=True)
|
||||
return 0.0
|
||||
|
||||
|
||||
def _calc_ta_score(code: str) -> dict:
|
||||
"""严谨技术指标计算:RSI/MACD/量能/背离——从stock_daily确定性计算。
|
||||
返回 {'rsi': float, 'macd_hist': float, 'macd_hist_prev': float, 'volume_trend': str, 'divergence': str}"""
|
||||
try:
|
||||
import technical_analysis as _ta_mod
|
||||
_ta_full = _ta_mod.full_analysis(code)
|
||||
if _ta_full and "error" not in _ta_full:
|
||||
_scores = _ta_full.get("scores", {})
|
||||
return {
|
||||
'rsi': _scores.get("rsi", 50),
|
||||
'macd_hist': _scores.get("macd_hist", 0),
|
||||
'macd_hist_prev': _scores.get("macd_hist_prev", 0),
|
||||
'volume_trend': _scores.get("volume_trend", "neutral"),
|
||||
'divergence': _scores.get("divergence", "none"),
|
||||
}
|
||||
except Exception as _e:
|
||||
print(f" [TA-SCORE] {code} 技术分计算失败: {_e}", flush=True)
|
||||
return {'rsi': 50, 'macd_hist': 0, 'macd_hist_prev': 0, 'volume_trend': "neutral", 'divergence': "none"}
|
||||
|
||||
|
||||
def compute_rec_score(conn, code: str) -> int:
|
||||
"""五维复合推荐评分 0-100。RR高≠值得买,趋势+行业+信号综合判断。
|
||||
维度:RR(0-35) + 信号(0-25) + 趋势(0-20) + 行业(0-10) + 区间(0-10)"""
|
||||
@@ -1358,6 +1374,21 @@ def compute_rec_score(conn, code: str) -> int:
|
||||
elif zone_pct >= 2: s_zone = 4
|
||||
else: s_zone = 2
|
||||
|
||||
# ── 5. 严谨技术指标(2026-07-28 老爸:不许伪造好看的RR)──
|
||||
_ta_s = _calc_ta_score(code)
|
||||
_rsi = _ta_s["rsi"]
|
||||
if 30 <= _rsi <= 70: s_trend += 3
|
||||
elif _rsi < 30: s_trend += 5
|
||||
elif _rsi > 70: s_trend -= 5
|
||||
_mh, _mp = _ta_s["macd_hist"], _ta_s["macd_hist_prev"]
|
||||
if _mh > 0 and _mh > _mp: s_trend += 5
|
||||
elif _mh < 0 and _mh < _mp: s_trend -= 5
|
||||
_vt = _ta_s["volume_trend"]
|
||||
if _vt == "accumulation": s_trend += 5
|
||||
elif _vt == "distribution": s_trend -= 5
|
||||
_div = _ta_s["divergence"]
|
||||
if _div == "bearish": s_trend -= 8
|
||||
elif _div == "bullish": s_trend += 5
|
||||
total = s_rr + s_sig + s_trend + s_sec + s_zone
|
||||
conn.execute(
|
||||
"UPDATE holding_strategies SET rec_score=? WHERE code=? AND status='active'",
|
||||
@@ -1443,8 +1474,17 @@ def sync_recommend_tag(conn, code: str, timing_signal: str):
|
||||
if timing_signal in _ACTION_BUY or timing_signal in _ACTION_SELL:
|
||||
# ── 买入RR质量门禁(2026-07-27 老爸:RR<2.0的买入不值得推荐,tag都不能打,防盯盘垃圾)──
|
||||
# 此前tag先打、enqueue再查RR,结果RR<2.0的tag已落盯盘,与XMPP不一致。
|
||||
# 已持仓股不再重复推荐(2026-07-28 老爸:已买了的票该在持仓不在推荐)
|
||||
_should_tag = True
|
||||
if timing_signal in _ACTION_BUY:
|
||||
_owned = conn.execute(
|
||||
"SELECT shares FROM holdings WHERE code=? AND is_active=1 AND shares>0",
|
||||
(code,)).fetchone()
|
||||
if _owned and _owned[0] and _owned[0] > 0:
|
||||
_should_tag = False # 已持仓,不再重复推荐
|
||||
if _old_tag == 'current_recommend':
|
||||
conn.execute("UPDATE holding_strategies SET tag='' WHERE code=? AND status='active'", (code,))
|
||||
conn.commit()
|
||||
elif timing_signal in _ACTION_BUY:
|
||||
_rr_chk = conn.execute(
|
||||
"SELECT rr_ratio FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
|
||||
if _rr_chk and (_rr_chk[0] or 0) < 2.0:
|
||||
@@ -1545,8 +1585,8 @@ def enqueue_recommend(conn, code: str):
|
||||
|
||||
|
||||
def track_strategy_version(conn, code: str):
|
||||
"""版本化策略追踪:每次策略变更(sync_recommend_tag 后)自动记录新版本。
|
||||
只在 tag='current_recommend' 时记录;与上一条相比有实质变更才追加新版本。"""
|
||||
"""版本化策略追踪:每次策略变更自动记录新版本。
|
||||
跟踪所有策略状态(不限于 tag='current_recommend'),tag 清除时也记录。"""
|
||||
try:
|
||||
row = conn.execute(
|
||||
"SELECT name, timing_signal, rec_score, rr_ratio, entry_low, entry_high, "
|
||||
@@ -1555,8 +1595,9 @@ def track_strategy_version(conn, code: str):
|
||||
if not row:
|
||||
return
|
||||
name, sig, score, rr, el, eh, sl, tp, pos, tag = row
|
||||
if tag != 'current_recommend':
|
||||
return # 非推荐状态不追踪
|
||||
# 空壳策略(无信号/无买入区)不追踪
|
||||
if not sig or (not el and not eh):
|
||||
return
|
||||
|
||||
lp = conn.execute("SELECT price FROM live_prices WHERE code=?", (code,)).fetchone()
|
||||
price = lp[0] if lp and lp[0] else 0
|
||||
|
||||
Reference in New Issue
Block a user