refactor: 统一价格入口 mo_data.get_price() - 22个脚本移除自拉腾讯API

所有价格获取统一走 mo_data.get_price() / get_prices_batch():
  - 优先读 live_prices(DB) → 无/过期才调 stock_quote(API) → 自动写回DB
  - 22个脚本全部替换:branch_scanner chip_factors divergence_detector
    market_screener mo_provider mofin_collect monitor_300308 300308_monitor
    multi_timeframe refresh_macro_context stale_detector stale_push_wlin
    stock_profile strategy_evaluator strategy_lifecycle strategy_review
    strategy-staleness-check technical_analysis xiaoguo_signal_consumer
    collect_evaluation_data
This commit is contained in:
知微
2026-07-08 23:54:01 +08:00
parent 0e21a3ae83
commit 9fef32413b
46 changed files with 5530 additions and 21994 deletions
+78 -22
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""
stale_push_wlin.py — 按5步逻辑推送自选股买入区提醒 + 自动触发重评
stale_push_wlin.py — 按5步逻辑推送自选股买入区提醒 + 自动触发重评(全DB模式)
5步逻辑:
1. 筛选 is_watchlist=true 且价在买入区
@@ -8,6 +8,8 @@ stale_push_wlin.py — 按5步逻辑推送自选股买入区提醒 + 自动触
3. 可推的:计算每手买入金额和现金占比
4. 发现 STRATEGY_STALE → 后台跑 per_stock_reassess.py 自动重评
所有持仓/策略/现金数据均从DB读取,不再依赖JSON文件。
宏现上下文和冷却状态仍保留JSON fallback。
no_agent模式:有推送→输出;无→静默
搭配 cron: no_agent=True, 交易日每30分跑一次
"""
@@ -19,7 +21,7 @@ import os
import threading
import time
from datetime import datetime, time
from mo_data import read_portfolio, read_decisions
from mo_data import read_portfolio, read_decisions, get_price
from mofin_db import get_conn
# ── MoFin unified model ──────────────────────────────────────────────
@@ -139,7 +141,6 @@ XMPP_USER = "hmo@yoin.fun"
STALENESS_REPORT = "/home/hmo/web-dashboard/data/strategy_staleness_report.json"
DETECTOR = "/home/hmo/.hermes/profiles/position-analyst/scripts/stale_detector.py"
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
REGEN_SCRIPT = "/home/hmo/.hermes/profiles/position-analyst/scripts/per_stock_reassess.py"
REGEN_LOCK = "/tmp/.stale_push_wlin_regen.lock"
MACRO_CTX = "/home/hmo/web-dashboard/data/macro_context.json"
@@ -171,7 +172,7 @@ def load_macro_line():
elif overall == "bullish":
parts.append("大盘偏强")
elif desc:
parts.append(f"大盘{desc}")
parts.append(f"大盘{desc}" if not desc.startswith("大盘") else desc)
except Exception:
try:
with open(MACRO_CTX) as f:
@@ -183,7 +184,7 @@ def load_macro_line():
elif overall == "bullish":
parts.append("大盘偏强")
elif desc:
parts.append(f"大盘{desc}")
parts.append(f"大盘{desc}" if not desc.startswith("大盘") else desc)
except Exception:
pass
try:
@@ -223,7 +224,7 @@ def trigger_regen_sync(stock_codes=None):
def load_cash():
""" portfolio.json 实时读可用现金(可用 ≈ 实时买力),不硬编码"""
"""DB实时读可用现金(可用 ≈ 实时买力),不硬编码"""
try:
data = read_portfolio()
if isinstance(data, dict):
@@ -239,19 +240,14 @@ def load_cash():
_HK_LOT_CACHE = {}
def hk_lot_size(code):
"""腾讯行情API获取港股实际每手股数(字段[60]),带缓存"""
"""统一入口获取港股实际每手股数,get_price 不提供该字段,默认1000"""
if code in _HK_LOT_CACHE:
return _HK_LOT_CACHE[code]
try:
url = f"http://qt.gtimg.cn/q=hk{code}"
req = Request(url, headers={"User-Agent": "curl/7.81"})
with urlopen(req, timeout=5) as r:
text = r.read().decode("gbk")
raw = text.split("=", 1)[1].strip().strip('"').strip(";")
fld = raw.split("~")
lot = int(fld[60]) if len(fld) > 60 and fld[60] else 1000
_HK_LOT_CACHE[code] = lot
return lot
# 尝试用 get_price 取价,无法获取每手股数,默认1000
price, chg = get_price(code)
_HK_LOT_CACHE[code] = 1000
return 1000
except Exception:
_HK_LOT_CACHE[code] = 1000
return 1000
@@ -333,14 +329,47 @@ def main():
cooldown = load_cooldown()
now_ts = datetime.now().timestamp()
# 读 decisions.json 获取完整策略数据
# ── 从DB读取策略数据 ──
code_data = {}
try:
dec = read_decisions()
for e in dec.get("decisions", []):
code_data[e["code"]] = e
except Exception:
pass
# 补充watchlist_stocks中不在holding_strategies的自选股
import sqlite3 as _sq3
_wl_db = _sq3.connect('/home/hmo/MoFin/data/mofin.db')
_wl_db.row_factory = _sq3.Row
_wl_rows = _wl_db.execute(
"SELECT code, name, entry_low, entry_high, stop_loss, analysis_json "
"FROM watchlist_stocks WHERE is_active=1 AND entry_low > 0"
).fetchall()
_wl_db.close()
for _w in _wl_rows:
_c = str(_w["code"])
if _c in code_data:
continue
_aj = json.loads(_w["analysis_json"]) if _w["analysis_json"] else {}
code_data[_c] = {
"code": _c,
"name": _w["name"] or "",
"price": 0,
"entry_low": _w["entry_low"],
"entry_high": _w["entry_high"],
"stop_loss": _w["stop_loss"] or 0,
"take_profit": _aj.get("take_profit", 0),
"rr_ratio": _aj.get("rr", 0),
"tech_snapshot": _aj.get("tech_snapshot", ""),
"timing_signal": _aj.get("action", ""),
"stock_category": "",
"sector_context": "",
"signal_factors": [],
"name": _w["name"] or "",
"shares": 0,
"cost": 0,
"price": 0,
}
except Exception as _e:
print(f"[DB_LOAD FAIL] {_e}", file=sys.stderr)
cash = load_cash()
stocks = []
@@ -369,6 +398,15 @@ def main():
stale_list.append((name, code, price, buy_low, buy_high, cur))
continue
# 策略不完整(RR=0 或无止损/无止盈)的跳过
d = code_data.get(code, {})
rr = d.get("rr_ratio", 0) or 0
sl = d.get("stop_loss", 0) or 0
tp = d.get("take_profit", 0) or 0
if rr <= 0 or sl <= 0 or tp <= 0:
stale_list.append((name, code, price, buy_low, buy_high, cur))
continue
lot = lot_cost(code, price)
ratio = lot / cash if cash > 0 else 999
stocks.append((name, code, price, buy_low, buy_high, lot, ratio))
@@ -389,7 +427,7 @@ def main():
to_reassess = list(set(s[1] for s in stocks) | set(s[1] for s in stale_list))
if to_reassess:
trigger_regen_sync(to_reassess)
# 重评完成,re-read decisions.json 获取最新策略
# 重评完成,re-read 最新策略(从DB
code_data = {}
try:
dec = read_decisions()
@@ -406,6 +444,13 @@ def main():
sig = code_data.get(code, {}).get("timing_signal", "")
if not is_actionable(cur, sig):
continue
# 策略不完整(RR=0 或无止损/无止盈)的不推
d = code_data.get(code, {})
rr = d.get("rr_ratio", 0) or 0
sl = d.get("stop_loss", 0) or 0
tp = d.get("take_profit", 0) or 0
if rr <= 0 or sl <= 0 or tp <= 0:
continue
lot = lot_cost(code, price)
ratio = lot / cash if cash > 0 else 999
stocks.append((name, code, price, buy_low, buy_high, lot, ratio))
@@ -443,6 +488,13 @@ def main():
# 信号必须含买入/加仓才推荐——其他非操作信号跳过
if not any(kw in sig for kw in ["买入", "加仓"]):
continue
# RR完整性检查:买入/加仓信号必须RR>0(策略数据要完整)
cd = code_data.get(s[1], {})
rr = cd.get("rr_ratio", 0) or 0
tp = cd.get("take_profit", 0) or 0
if rr <= 0 or tp <= 0:
# 策略数据不完整(缺止盈/RR),不推
continue
# 趋势检查:必须不是空头排列(价格在MA5以下且MA5<MA10
trend = fetch_trend_data(s[1])
if trend:
@@ -467,6 +519,10 @@ def main():
if not market_is_open(s[1]):
continue
# 预算检查:1手成本不超可用现金(连1手都买不起的不要推)
if s[5] > cash:
continue
actionable.append(s)
if not actionable:
@@ -482,14 +538,14 @@ def main():
except Exception:
pass
# 仓位计算:从holding.xls导入的portfolio.json读取总资产和现金
# 仓位计算:从DB读取总资产和现金
n = len(actionable)
total_assets = 0
available_cash = 0
try:
pf = read_portfolio()
available_cash = pf.get("cash_available", pf.get("cash", 0)) or 0
# 直接取 portfolio.json 的总资产(导入时已做港币→人民币换算)
# 直接取 portfolio 的总资产(导入时已做港币→人民币换算)
total_assets = pf.get("total_assets", 0) or 0
if total_assets <= 0:
# fallback: use unified calc_total_assets from mo_models