feat: add watchlist auto-reassessment to per_stock_reassess

- Add scan_watchlist_stocks() function that scans watchlist_stocks table
- Deviation formula: max(|price-entry_low|,|price-entry_high|)/entry_low*100 > 20%
- Calls technical_analysis.full_analysis() to get latest support/resistance
- Updates entry_low/entry_high/stop_loss/price/analysis_json in DB
- Records changelog in analysis_json with old/new values
- Limits to 3 stocks per run, logs remaining=N for overflow
- Scans watchlist AFTER decisions scan in main() (no changes to decisions logic)
This commit is contained in:
知微
2026-07-07 11:48:45 +08:00
parent 43bd84a7c0
commit 9f44715b61
+151
View File
@@ -216,6 +216,157 @@ def main():
print(f"[DONE] {ok}成功 {skipped}跳过 {errors}失败")
# ── 第二步:扫描自选股(watchlist),价格偏离买入区>20%触发重评 ──
scan_watchlist_stocks()
# ════════════════════════════════════════════════════════════════════
# 自选股扫描
# ════════════════════════════════════════════════════════════════════
def scan_watchlist_stocks():
"""扫描自选股表 (watchlist_stocks),对价格偏离买入区 >20% 的股票自动重评。
偏离公式: max(|price - entry_low|, |price - entry_high|) / entry_low * 100 > 20
通过 technical_analysis.full_analysis() 获取最新支撑/阻力位,
更新 entry_low / entry_high / stop_loss / price / analysis_json。
每轮最多处理 3 只,超过时标记剩余数量待下次扫描。
"""
import sqlite3, json
from datetime import datetime
from technical_analysis import full_analysis
from mo_models import is_hk_stock
DB = '/home/hmo/web-dashboard/data/mofin.db'
db = sqlite3.connect(DB)
db.row_factory = sqlite3.Row
rows = db.execute(
"SELECT * FROM watchlist_stocks WHERE is_active=1"
).fetchall()
if not rows:
print("[WL-SCAN] 自选股表为空,跳过")
db.close()
return
# ── 筛选偏离 >20% 的股票 ──
candidates = [] # (code, name, price, entry_low, entry_high, stop_loss, deviation, analysis_json)
for r in rows:
code = r["code"]
name = r["name"]
price = r["price"] or 0
entry_low = r["entry_low"] or 0
entry_high = r["entry_high"] or 0
stop_loss = r["stop_loss"] or 0
analysis_json = r["analysis_json"]
if entry_low <= 0 or price <= 0:
continue
dev_low = abs(price - entry_low)
dev_high = abs(price - entry_high)
deviation = max(dev_low, dev_high) / entry_low * 100
if deviation > 20:
candidates.append((code, name, price, entry_low, entry_high, stop_loss, deviation, analysis_json))
total_needed = len(candidates)
print(f"[WL-SCAN] 自选股共{len(rows)}只,偏离>20%需重评: {total_needed}")
MAX_PER_RUN = 3
to_process = candidates[:MAX_PER_RUN]
remaining = max(0, total_needed - MAX_PER_RUN)
if remaining > 0:
print(f"[WL-SCAN] 本轮限{MAX_PER_RUN}只,剩余{remaining}只待下次扫描")
if not to_process:
print("[WL-SCAN] 无需重评")
db.close()
return
ok = 0
errors = 0
for code, name, price, old_low, old_high, old_stop, deviation, old_analysis_json in to_process:
print(f"[WL-REASSESS] {code} {name}: 偏离{deviation:.1f}%,触发重评")
try:
ta = full_analysis(code)
if not ta or "error" in ta:
print(f" [WARN] TA失败: {ta}")
errors += 1
continue
sr = ta.get("support_resistance", {})
if "error" in sr:
print(f" [WARN] 支撑/阻力计算失败: {sr}")
errors += 1
continue
new_price = ta.get("quote", {}).get("price", price)
new_entry_low = round(sr.get("weak_support", old_low), 2)
new_entry_high = round(sr.get("weak_resist", old_high), 2)
new_stop_loss = round(sr.get("strong_support", old_stop), 2)
new_take_profit = round(sr.get("strong_resist", 0), 2)
# ── 更新 analysis_json + changelog ──
old_analysis = json.loads(old_analysis_json) if old_analysis_json else {}
changelog = old_analysis.get("changelog", [])
changelog.append({
"action": "auto_reassess_watchlist",
"reason": f"价格偏离买入区{deviation:.1f}%",
"old_entry_low": old_low,
"old_entry_high": old_high,
"new_entry_low": new_entry_low,
"new_entry_high": new_entry_high,
"old_stop_loss": old_stop,
"new_stop_loss": new_stop_loss,
"take_profit": new_take_profit,
"price": new_price,
"deviation_pct": round(deviation, 1),
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M"),
})
new_analysis = {
**old_analysis,
"take_profit": new_take_profit,
"tech_snapshot": {
"support_resistance": sr,
"candlestick": ta.get("candlestick", {}),
"volume": ta.get("volume", {}),
"analyzed_at": ta.get("analyzed_at", ""),
},
"reassessed_at": datetime.now().strftime("%Y-%m-%d"),
"changelog": changelog,
}
currency = "HKD" if is_hk_stock(code) else "CNY"
db.execute("""
UPDATE watchlist_stocks
SET entry_low=?, entry_high=?, stop_loss=?, price=?,
currency=?, analysis_json=?
WHERE code=? AND is_active=1
""", (
new_entry_low, new_entry_high, new_stop_loss,
new_price, currency, json.dumps(new_analysis, ensure_ascii=False),
code,
))
db.commit()
print(f" [OK] {code} {name}: 买入区{old_low}-{old_high} -> {new_entry_low}-{new_entry_high}, "
f"止损{new_stop_loss}, 止盈{new_take_profit}")
ok += 1
except Exception as e:
import traceback
print(f" [ERROR] {code}: {e}", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
errors += 1
db.close()
remaining_msg = f" (剩余{remaining}只)" if remaining else ""
print(f"[WL-SCAN] DONE: {ok}成功 {errors}失败{remaining_msg}")
if __name__ == "__main__":
main()